From 8b2d5ad1120551d9398a3aa1cf7e6467d9f40e7d Mon Sep 17 00:00:00 2001
From: 云 <2163098428@qq.com>
Date: 星期四, 13 八月 2026 10:49:23 +0800
Subject: [PATCH] feat(mes): 添加计量器具管理功能
---
src/api/mes/dv/meteringcheck/index.ts | 60 ++
dist.zip | 0
src/views/mes/dv/metering/data.ts | 437 +++++++++++++++
src/views/mes/dv/metering/components/select.vue | 142 ++++
src/views/mes/dv/meteringcheck/modules/form.vue | 99 +++
src/views/mes/dv/metering/modules/form.vue | 99 +++
src/views/mes/dv/metering/components/index.ts | 2
src/packages/constants/src/biz-mes-enum.ts | 20
src/views/mes/dv/metering/index.vue | 176 ++++++
src/api/mes/dv/metering/index.ts | 72 ++
src/views/mes/dv/metering/components/select-dialog.vue | 213 +++++++
src/packages/constants/src/dict-enum.ts | 3
src/views/mes/dv/meteringcheck/index.vue | 158 +++++
src/views/mes/dv/meteringcheck/data.ts | 218 +++++++
14 files changed, 1,699 insertions(+), 0 deletions(-)
diff --git a/dist.zip b/dist.zip
new file mode 100644
index 0000000..b695091
--- /dev/null
+++ b/dist.zip
Binary files differ
diff --git a/src/api/mes/dv/metering/index.ts b/src/api/mes/dv/metering/index.ts
new file mode 100644
index 0000000..55f0e7e
--- /dev/null
+++ b/src/api/mes/dv/metering/index.ts
@@ -0,0 +1,72 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+export namespace MesDvMeteringApi {
+ /** MES 璁¢噺鍣ㄥ叿 */
+ export interface Metering {
+ id?: number; // 涓婚敭ID
+ code?: string; // 璁¢噺鍣ㄥ叿缂栫爜
+ name?: string; // 璁¢噺鍣ㄥ叿鍚嶇О
+ specification?: string; // 瑙勬牸鍨嬪彿
+ brand?: string; // 鍝佺墝/鐢熶骇鍘傚
+ category?: number; // 鍣ㄥ叿绫诲埆锛堝瓧鍏革級
+ precision?: string; // 绮惧害
+ range?: string; // 閲忕▼
+ deptId?: number; // 浣跨敤閮ㄩ棬缂栧彿
+ deptName?: string; // 浣跨敤閮ㄩ棬鍚嶇О
+ chargeUserId?: number; // 璐d换浜虹紪鍙�
+ chargeUserNickname?: string; // 璐d换浜烘樀绉�
+ checkCycleCount?: number; // 鏍″噯鍛ㄦ湡锛堟湀锛�
+ useDate?: string; // 鎶曞叆浣跨敤鏃ユ湡
+ lastCheckDate?: string; // 涓婃妫�娴嬫棩鏈�
+ nextCheckDate?: string; // 涓嬫妫�娴嬫棩鏈燂紙鍒版湡鏃ワ級
+ status?: number; // 鐘舵�侊紙瀛楀吀锛�
+ expireStatus?: number; // 鍒版湡鐘舵�侊細1 宸茶繃鏈� / 2 鍗冲皢鍒版湡 / 3 姝e父
+ expireDays?: number; // 璺濆埌鏈熷ぉ鏁�
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ璁¢噺鍣ㄥ叿鍒嗛〉 */
+export function getMeteringPage(params: PageParam) {
+ return requestClient.get<PageResult<MesDvMeteringApi.Metering>>(
+ '/mes/dv/metering/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ璁¢噺鍣ㄥ叿璇︽儏 */
+export function getMetering(id: number) {
+ return requestClient.get<MesDvMeteringApi.Metering>(
+ `/mes/dv/metering/get?id=${id}`,
+ );
+}
+
+/** 鏂板璁¢噺鍣ㄥ叿 */
+export function createMetering(data: MesDvMeteringApi.Metering) {
+ return requestClient.post<number>('/mes/dv/metering/create', data);
+}
+
+/** 淇敼璁¢噺鍣ㄥ叿 */
+export function updateMetering(data: MesDvMeteringApi.Metering) {
+ return requestClient.put('/mes/dv/metering/update', data);
+}
+
+/** 鍒犻櫎璁¢噺鍣ㄥ叿 */
+export function deleteMetering(id: number) {
+ return requestClient.delete(`/mes/dv/metering/delete?id=${id}`);
+}
+
+/** 瀵煎嚭璁¢噺鍣ㄥ叿 */
+export function exportMetering(params: any) {
+ return requestClient.download('/mes/dv/metering/export-excel', { params });
+}
+
+/** 鏌ヨ璁¢噺鍣ㄥ叿绮剧畝鍒楄〃锛堜笅鎷夐�夐」锛� */
+export function getMeteringSimpleList() {
+ return requestClient.get<MesDvMeteringApi.Metering[]>(
+ '/mes/dv/metering/simple-list',
+ );
+}
diff --git a/src/api/mes/dv/meteringcheck/index.ts b/src/api/mes/dv/meteringcheck/index.ts
new file mode 100644
index 0000000..e8995ec
--- /dev/null
+++ b/src/api/mes/dv/meteringcheck/index.ts
@@ -0,0 +1,60 @@
+import type { PageParam, PageResult } from '@vben/request';
+
+import { requestClient } from '#/api/request';
+
+export namespace MesDvMeteringCheckApi {
+ /** MES 璁¢噺妫�娴嬭褰� */
+ export interface MeteringCheck {
+ id?: number; // 涓婚敭ID
+ meteringId?: number; // 璁¢噺鍣ㄥ叿ID
+ meteringCode?: string; // 璁¢噺鍣ㄥ叿缂栫爜
+ meteringName?: string; // 璁¢噺鍣ㄥ叿鍚嶇О
+ checkCode?: string; // 妫�娴嬬紪鍙�
+ checkDate?: string; // 妫�娴嬫棩鏈�
+ checkOrg?: string; // 妫�娴嬫満鏋�
+ checkResult?: number; // 妫�娴嬬粨璁猴紙瀛楀吀锛�
+ certNo?: string; // 璇佷功缂栧彿
+ checkUserId?: number; // 妫�娴嬩汉缂栧彿
+ checkUserNickname?: string; // 妫�娴嬩汉鏄电О
+ nextCheckDate?: string; // 涓嬫妫�娴嬫棩鏈�
+ remark?: string; // 澶囨敞
+ createTime?: Date; // 鍒涘缓鏃堕棿
+ }
+}
+
+/** 鏌ヨ璁¢噺妫�娴嬭褰曞垎椤� */
+export function getMeteringCheckPage(params: PageParam) {
+ return requestClient.get<PageResult<MesDvMeteringCheckApi.MeteringCheck>>(
+ '/mes/dv/metering-check/page',
+ { params },
+ );
+}
+
+/** 鏌ヨ璁¢噺妫�娴嬭褰曡鎯� */
+export function getMeteringCheck(id: number) {
+ return requestClient.get<MesDvMeteringCheckApi.MeteringCheck>(
+ `/mes/dv/metering-check/get?id=${id}`,
+ );
+}
+
+/** 鏂板璁¢噺妫�娴嬭褰� */
+export function createMeteringCheck(data: MesDvMeteringCheckApi.MeteringCheck) {
+ return requestClient.post<number>('/mes/dv/metering-check/create', data);
+}
+
+/** 淇敼璁¢噺妫�娴嬭褰� */
+export function updateMeteringCheck(data: MesDvMeteringCheckApi.MeteringCheck) {
+ return requestClient.put('/mes/dv/metering-check/update', data);
+}
+
+/** 鍒犻櫎璁¢噺妫�娴嬭褰� */
+export function deleteMeteringCheck(id: number) {
+ return requestClient.delete(`/mes/dv/metering-check/delete?id=${id}`);
+}
+
+/** 瀵煎嚭璁¢噺妫�娴嬭褰� */
+export function exportMeteringCheck(params: any) {
+ return requestClient.download('/mes/dv/metering-check/export-excel', {
+ params,
+ });
+}
diff --git a/src/packages/constants/src/biz-mes-enum.ts b/src/packages/constants/src/biz-mes-enum.ts
index 1ac585c..435b1db 100644
--- a/src/packages/constants/src/biz-mes-enum.ts
+++ b/src/packages/constants/src/biz-mes-enum.ts
@@ -104,6 +104,26 @@
ABNORMAL: 2,
} as const;
+/** MES 璁¢噺鍣ㄥ叿鐘舵�佹灇涓� */
+export const MesDvMeteringStatusEnum = {
+ IN_USE: 1, // 鍦ㄧ敤
+ STOPPED: 2, // 鍋滅敤
+ SCRAPPED: 3, // 鎶ュ簾
+} as const;
+
+/** MES 璁¢噺妫�娴嬬粨璁烘灇涓� */
+export const MesDvMeteringCheckResultEnum = {
+ QUALIFIED: 1, // 鍚堟牸
+ UNQUALIFIED: 2, // 涓嶅悎鏍�
+} as const;
+
+/** MES 璁¢噺鍣ㄥ叿鍒版湡鐘舵�佹灇涓� */
+export const MesDvMeteringExpireStatusEnum = {
+ OVERDUE: 1, // 宸茶繃鏈�
+ WITHIN: 2, // 鍗冲皢鍒版湡
+ NORMAL: 3, // 姝e父
+} as const;
+
/** MES 缁翠慨宸ュ崟鐘舵�佹灇涓� */
export const MesDvRepairStatusEnum = {
PREPARE: MesOrderStatusConstants.DRAFT,
diff --git a/src/packages/constants/src/dict-enum.ts b/src/packages/constants/src/dict-enum.ts
index 8731055..e74a983 100644
--- a/src/packages/constants/src/dict-enum.ts
+++ b/src/packages/constants/src/dict-enum.ts
@@ -234,6 +234,9 @@
MES_DV_REPAIR_RESULT: 'mes_dv_repair_result', // MES 缁翠慨缁撴灉
MES_DV_CHECK_RECORD_STATUS: 'mes_dv_check_record_status', // MES 鐐规璁板綍鐘舵��
MES_DV_CHECK_RESULT: 'mes_dv_check_result', // MES 鐐规缁撴灉
+ MES_DV_METERING_CATEGORY: 'mes_dv_metering_category', // MES 璁¢噺鍣ㄥ叿绫诲埆
+ MES_DV_METERING_STATUS: 'mes_dv_metering_status', // MES 璁¢噺鍣ㄥ叿鐘舵��
+ MES_DV_METERING_CHECK_RESULT: 'mes_dv_metering_check_result', // MES 璁¢噺妫�娴嬬粨璁�
MES_PRO_LINK_TYPE: 'mes_pro_link_type', // MES 宸ュ簭鍏崇郴绫诲瀷
MES_PRO_WORK_ORDER_STATUS: 'mes_pro_work_order_status', // MES 鐢熶骇宸ュ崟鐘舵��
MES_PRO_WORK_ORDER_TYPE: 'mes_pro_work_order_type', // MES 宸ュ崟绫诲瀷
diff --git a/src/views/mes/dv/metering/components/index.ts b/src/views/mes/dv/metering/components/index.ts
new file mode 100644
index 0000000..feacb92
--- /dev/null
+++ b/src/views/mes/dv/metering/components/index.ts
@@ -0,0 +1,2 @@
+export { default as DvMeteringSelectDialog } from './select-dialog.vue';
+export { default as DvMeteringSelect } from './select.vue';
diff --git a/src/views/mes/dv/metering/components/select-dialog.vue b/src/views/mes/dv/metering/components/select-dialog.vue
new file mode 100644
index 0000000..5d2cd21
--- /dev/null
+++ b/src/views/mes/dv/metering/components/select-dialog.vue
@@ -0,0 +1,213 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesDvMeteringApi } from '#/api/mes/dv/metering';
+
+import { nextTick, ref } from 'vue';
+
+import { message, Modal } from 'ant-design-vue';
+
+import { useVbenVxeGrid } from '#/adapter/vxe-table';
+import { getMeteringPage } from '#/api/mes/dv/metering';
+
+import {
+ useMeteringSelectGridColumns,
+ useMeteringSelectGridFormSchema,
+} from '../data';
+
+defineOptions({ name: 'DvMeteringSelectDialog' });
+
+const emit = defineEmits<{
+ selected: [rows: MesDvMeteringApi.Metering[]];
+}>();
+
+const open = ref(false); // 寮圭獥鏄惁鎵撳紑
+const multiple = ref(false); // 鏄惁澶氶��
+const selectedRows = ref<MesDvMeteringApi.Metering[]>([]); // 宸查�夊櫒鍏峰垪琛�
+const preSelectedIds = ref<number[]>([]); // 棰勯�夊櫒鍏风紪鍙峰垪琛�
+
+/** 鑾峰彇褰撳墠琛ㄦ牸鏁版嵁 */
+function getTableRows() {
+ return gridApi.grid.getTableData().fullData as MesDvMeteringApi.Metering[];
+}
+
+/** 鑾峰彇澶氶�夎褰曪紝鍖呭惈 VXE reserve 璺ㄩ〉璁板綍 */
+function getMultipleSelectedRows() {
+ const selectedMap = new Map<number, MesDvMeteringApi.Metering>();
+ const records = [
+ ...(gridApi.grid.getCheckboxReserveRecords?.() ?? []),
+ ...(gridApi.grid.getCheckboxRecords?.() ?? []),
+ ] as MesDvMeteringApi.Metering[];
+ records.forEach((row) => {
+ const rowId = row.id;
+ if (rowId !== undefined && rowId !== null) {
+ selectedMap.set(rowId, row);
+ }
+ });
+ return [...selectedMap.values()];
+}
+
+/** 澶勭悊鍕鹃�夊彉鍖� */
+function handleCheckboxSelectChange() {
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊鍗曢�夊彉鍖� */
+function handleRadioChange(row: MesDvMeteringApi.Metering) {
+ selectedRows.value = [row];
+}
+
+/** 澶氶�夋ā寮忎笅鍒囨崲琛屽嬀閫� */
+async function toggleMultipleRow(row: MesDvMeteringApi.Metering) {
+ const selected = gridApi.grid.isCheckedByCheckboxRow(row);
+ await gridApi.grid.setCheckboxRow(row, !selected);
+ selectedRows.value = getMultipleSelectedRows();
+}
+
+/** 澶勭悊琛屽弻鍑� */
+async function handleCellDblclick({
+ row,
+}: {
+ row: MesDvMeteringApi.Metering;
+}) {
+ if (multiple.value) {
+ await toggleMultipleRow(row);
+ return;
+ }
+ selectedRows.value = [row];
+ await gridApi.grid.setRadioRow(row);
+ handleConfirm();
+}
+
+/** 鍥炴樉棰勯�夊櫒鍏� */
+async function applyPreSelection() {
+ if (preSelectedIds.value.length === 0) {
+ return;
+ }
+ const rows = getTableRows();
+ for (const row of rows) {
+ if (
+ row.id === undefined ||
+ row.id === null ||
+ !preSelectedIds.value.includes(row.id)
+ ) {
+ continue;
+ }
+ if (multiple.value) {
+ await gridApi.grid.setCheckboxRow(row, true);
+ } else {
+ await gridApi.grid.setRadioRow(row);
+ selectedRows.value = [row];
+ return;
+ }
+ }
+ if (multiple.value) {
+ selectedRows.value = getMultipleSelectedRows();
+ }
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useMeteringSelectGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useMeteringSelectGridColumns(false),
+ height: 480,
+ keepSource: true,
+ checkboxConfig: {
+ highlight: true,
+ range: true,
+ reserve: true,
+ },
+ radioConfig: {
+ highlight: true,
+ trigger: 'row',
+ },
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) => {
+ return await getMeteringPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ });
+ },
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesDvMeteringApi.Metering>,
+ gridEvents: {
+ cellDblclick: handleCellDblclick,
+ checkboxAll: handleCheckboxSelectChange,
+ checkboxChange: handleCheckboxSelectChange,
+ radioChange: ({ row }: { row: MesDvMeteringApi.Metering }) => {
+ handleRadioChange(row);
+ },
+ },
+});
+
+/** 閲嶇疆鏌ヨ鍜岄�夋嫨鐘舵�� */
+async function resetQueryState() {
+ selectedRows.value = [];
+ await gridApi.grid.clearCheckboxRow();
+ await gridApi.grid.clearCheckboxReserve();
+ await gridApi.grid.clearRadioRow();
+ await gridApi.formApi.resetForm();
+}
+
+/** 鎵撳紑璁¢噺鍣ㄥ叿閫夋嫨寮圭獥 */
+async function openModal(
+ selectedIds?: number[],
+ options?: { multiple?: boolean },
+) {
+ open.value = true;
+ multiple.value = options?.multiple ?? false;
+ preSelectedIds.value = selectedIds || [];
+ await nextTick();
+ gridApi.setGridOptions({
+ columns: useMeteringSelectGridColumns(multiple.value),
+ });
+ await resetQueryState();
+ await gridApi.query();
+ await nextTick();
+ await applyPreSelection();
+}
+
+/** 鍏抽棴璁¢噺鍣ㄥ叿閫夋嫨寮圭獥 */
+async function closeModal() {
+ open.value = false;
+ await resetQueryState();
+}
+
+/** 纭閫夋嫨璁¢噺鍣ㄥ叿 */
+function handleConfirm() {
+ const rows = multiple.value ? getMultipleSelectedRows() : selectedRows.value;
+ if (rows.length === 0) {
+ message.warning(multiple.value ? '璇疯嚦灏戦�夋嫨涓�鏉℃暟鎹�' : '璇烽�夋嫨涓�鏉℃暟鎹�');
+ return;
+ }
+ emit('selected', multiple.value ? rows : [rows[0]!]);
+ 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="璁¢噺鍣ㄥ叿鍒楄〃" />
+ </Modal>
+</template>
diff --git a/src/views/mes/dv/metering/components/select.vue b/src/views/mes/dv/metering/components/select.vue
new file mode 100644
index 0000000..018e89d
--- /dev/null
+++ b/src/views/mes/dv/metering/components/select.vue
@@ -0,0 +1,142 @@
+<script lang="ts" setup>
+import type { MesDvMeteringApi } from '#/api/mes/dv/metering';
+
+import { computed, ref, useAttrs, watch } from 'vue';
+
+import { IconifyIcon } from '@vben/icons';
+
+import { Input, Tooltip } from 'ant-design-vue';
+
+import { getMetering } from '#/api/mes/dv/metering';
+
+import DvMeteringSelectDialog from './select-dialog.vue';
+
+defineOptions({ name: 'DvMeteringSelect', 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: MesDvMeteringApi.Metering | undefined];
+ 'update:modelValue': [value: number | undefined];
+}>();
+const attrs = useAttrs(); // 閫忎紶灞炴��
+const dialogRef = ref<InstanceType<typeof DvMeteringSelectDialog>>(); // 璁¢噺鍣ㄥ叿閫夋嫨寮圭獥
+const hovering = ref(false); // 鏄惁鎮仠
+const selectedItem = ref<MesDvMeteringApi.Metering>(); // 褰撳墠閫変腑鐨勮閲忓櫒鍏�
+
+const displayLabel = computed(() => selectedItem.value?.name ?? ''); // 閫夋嫨鍣ㄥ睍绀哄悕绉�
+const showClear = computed(
+ () =>
+ props.allowClear &&
+ !props.disabled &&
+ hovering.value &&
+ props.modelValue !== null,
+);
+
+/** 鏍规嵁鍣ㄥ叿缂栧彿鍥炴樉閫夋嫨鍣� */
+async function resolveItemById(id: number | undefined) {
+ if (!id) {
+ selectedItem.value = undefined;
+ return;
+ }
+ if (selectedItem.value?.id === id) {
+ return;
+ }
+ selectedItem.value = await getMetering(id);
+}
+
+watch(
+ () => props.modelValue,
+ (value) => {
+ resolveItemById(value);
+ },
+ { immediate: true },
+);
+
+/** 娓呯┖宸查�夊櫒鍏� */
+function clearSelected() {
+ selectedItem.value = undefined;
+ emit('update:modelValue', undefined);
+ emit('change', undefined);
+}
+
+/** 鎵撳紑璁¢噺鍣ㄥ叿閫夋嫨寮圭獥 */
+function handleClick(event: MouseEvent) {
+ if (props.disabled) {
+ return;
+ }
+ const target = event.target as HTMLElement;
+ if (showClear.value && target.closest('.ant-input-suffix')) {
+ event.stopPropagation();
+ clearSelected();
+ return;
+ }
+ const selectedIds = (
+ props.modelValue === null ? [] : [props.modelValue]
+ ) as number[];
+ dialogRef.value?.open(selectedIds, { multiple: false });
+}
+
+/** 鍥炲~閫変腑鐨勮閲忓櫒鍏� */
+function handleSelected(rows: MesDvMeteringApi.Metering[]) {
+ const item = rows[0];
+ if (!item) {
+ return;
+ }
+ selectedItem.value = item;
+ emit('update:modelValue', item.id);
+ emit('change', item);
+}
+</script>
+
+<template>
+ <div
+ v-bind="attrs"
+ class="w-full"
+ :class="disabled ? 'cursor-not-allowed' : 'cursor-pointer'"
+ @click="handleClick"
+ @mouseenter="hovering = true"
+ @mouseleave="hovering = false"
+ >
+ <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 v-if="selectedItem.specification">
+ 瑙勬牸鍨嬪彿锛歿{ selectedItem.specification }}
+ </div>
+ <div v-if="selectedItem.deptName">
+ 浣跨敤閮ㄩ棬锛歿{ selectedItem.deptName }}
+ </div>
+ </div>
+ </template>
+ <Input
+ :disabled="disabled"
+ :placeholder="placeholder"
+ :value="displayLabel"
+ readonly
+ >
+ <template #suffix>
+ <IconifyIcon
+ class="size-4"
+ :icon="showClear ? 'lucide:circle-x' : 'lucide:search'"
+ />
+ </template>
+ </Input>
+ </Tooltip>
+ </div>
+ <DvMeteringSelectDialog ref="dialogRef" @selected="handleSelected" />
+</template>
diff --git a/src/views/mes/dv/metering/data.ts b/src/views/mes/dv/metering/data.ts
new file mode 100644
index 0000000..1f57561
--- /dev/null
+++ b/src/views/mes/dv/metering/data.ts
@@ -0,0 +1,437 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesDvMeteringApi } from '#/api/mes/dv/metering';
+
+import {
+ DICT_TYPE,
+ MesDvMeteringExpireStatusEnum,
+ MesDvMeteringStatusEnum,
+} from '@vben/constants';
+import { getDictOptions } from '@vben/hooks';
+import { handleTree } from '@vben/utils';
+
+import { z } from '#/adapter/form';
+import { getSimpleDeptList } from '#/api/system/dept';
+import { getSimpleUserList } from '#/api/system/user';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+/** 鍒版湡鐘舵�佸睍绀洪厤缃� */
+export const METERING_EXPIRE_STATUS_OPTIONS: {
+ color: string;
+ label: string;
+ value: number;
+}[] = [
+ {
+ label: '宸茶繃鏈�',
+ value: MesDvMeteringExpireStatusEnum.OVERDUE,
+ color: 'red',
+ },
+ {
+ label: '鍗冲皢鍒版湡',
+ value: MesDvMeteringExpireStatusEnum.WITHIN,
+ color: 'orange',
+ },
+ {
+ label: '姝e父',
+ value: MesDvMeteringExpireStatusEnum.NORMAL,
+ color: 'green',
+ },
+];
+
+/** 鏂板/淇敼璁¢噺鍣ㄥ叿鐨勮〃鍗� */
+export function useFormSchema(formType: FormType): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'code',
+ label: '鍣ㄥ叿缂栫爜',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ櫒鍏风紪鐮�',
+ },
+ dependencies: {
+ triggerFields: ['id'],
+ componentProps: (values) => ({ disabled: !!values.id }),
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'name',
+ label: '鍣ㄥ叿鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ櫒鍏峰悕绉�',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'category',
+ label: '鍣ㄥ叿绫诲埆',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_DV_METERING_CATEGORY, 'number'),
+ placeholder: '璇烽�夋嫨鍣ㄥ叿绫诲埆',
+ },
+ help: '濡傚崱灏恒�佸帇鍔涜〃銆佹俯搴﹁绛夛紝涓嶅悓绫诲埆鏈夊父瑙佺殑鏍″噯鍛ㄦ湡',
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'specification',
+ label: '瑙勬牸鍨嬪彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヨ鏍煎瀷鍙�',
+ },
+ },
+ {
+ fieldName: 'brand',
+ label: '鍝佺墝/鐢熶骇鍘傚',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ搧鐗�/鐢熶骇鍘傚',
+ },
+ },
+ {
+ fieldName: 'precision',
+ label: '绮惧害',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ョ簿搴�',
+ },
+ help: '娴嬮噺绮惧害锛屽 0.01mm',
+ },
+ {
+ fieldName: 'range',
+ label: '閲忕▼',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ラ噺绋�',
+ },
+ help: '娴嬮噺鑼冨洿锛屽 0~150mm',
+ },
+ {
+ fieldName: 'deptId',
+ label: '浣跨敤閮ㄩ棬',
+ component: 'ApiTreeSelect',
+ componentProps: {
+ api: async () => handleTree(await getSimpleDeptList()),
+ childrenField: 'children',
+ labelField: 'name',
+ placeholder: '璇烽�夋嫨浣跨敤閮ㄩ棬',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'chargeUserId',
+ label: '璐d换浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨璐d换浜�',
+ valueField: 'id',
+ },
+ help: '璇ュ櫒鍏风殑缁存姢璐d换浜猴紝鍒版湡鎻愰啋鐨勭珯鍐呬俊浼氬彂缁欎粬',
+ },
+ {
+ fieldName: 'checkCycleCount',
+ label: '鏍″噯鍛ㄦ湡',
+ component: 'InputNumber',
+ componentProps: {
+ class: '!w-full',
+ min: 1,
+ precision: 0,
+ placeholder: '璇疯緭鍏ユ牎鍑嗗懆鏈�',
+ suffix: '鏈�',
+ },
+ help: '鍣ㄥ叿姣忛殧 N 涓湀椤绘牎鍑�/妫�瀹氫竴娆★紱鏂板妫�娴嬭褰曟椂涓嶅~涓嬫妫�娴嬫棩鏈燂紝灏嗘寜銆屾娴嬫棩鏈� + 鏍″噯鍛ㄦ湡銆嶈嚜鍔ㄨ绠�',
+ rules: 'required',
+ },
+ {
+ fieldName: 'useDate',
+ label: '鎶曞叆浣跨敤鏃ユ湡',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨鎶曞叆浣跨敤鏃ユ湡',
+ valueFormat: 'YYYY-MM-DD',
+ },
+ help: '鍣ㄥ叿寮�濮嬫姇鍏ヤ娇鐢ㄧ殑鏃ユ湡锛堜粎浣滆褰曪級',
+ },
+ {
+ fieldName: 'lastCheckDate',
+ label: '涓婃妫�娴嬫棩鏈�',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ disabled: true,
+ format: 'YYYY-MM-DD',
+ placeholder: '涓婃妫�娴嬫棩鏈�',
+ valueFormat: 'YYYY-MM-DD',
+ },
+ help: '鐢辨渶鏂颁竴鏉℃娴嬭褰曡嚜鍔ㄥ洖鍐欙紝涓嶅彲鎵嬪姩淇敼',
+ dependencies: {
+ triggerFields: ['id'],
+ show: () => formType === 'detail',
+ },
+ },
+ {
+ fieldName: 'nextCheckDate',
+ label: '涓嬫妫�娴嬫棩鏈�',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ disabled: true,
+ format: 'YYYY-MM-DD',
+ placeholder: '涓嬫妫�娴嬫棩鏈�',
+ valueFormat: 'YYYY-MM-DD',
+ },
+ help: '鍒版湡鏃ワ紝鐢辨娴嬭褰曡嚜鍔ㄨ绠楀洖鍐欙紱鍒版湡鍓� N 澶╋紙鍩虹璁炬柦閰嶇疆锛夊皢鎻愰啋璐d换浜�',
+ dependencies: {
+ triggerFields: ['id'],
+ show: () => formType === 'detail',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鍣ㄥ叿鐘舵��',
+ component: 'RadioGroup',
+ componentProps: {
+ buttonStyle: 'solid',
+ optionType: 'button',
+ options: getDictOptions(DICT_TYPE.MES_DV_METERING_STATUS, 'number'),
+ },
+ help: '鍦ㄧ敤/鍋滅敤/鎶ュ簾',
+ rules: z.number().default(MesDvMeteringStatusEnum.IN_USE),
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ 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: 'category',
+ label: '鍣ㄥ叿绫诲埆',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_DV_METERING_CATEGORY, 'number'),
+ placeholder: '璇烽�夋嫨鍣ㄥ叿绫诲埆',
+ },
+ },
+ {
+ fieldName: 'deptId',
+ label: '浣跨敤閮ㄩ棬',
+ component: 'ApiTreeSelect',
+ componentProps: {
+ allowClear: true,
+ api: async () => handleTree(await getSimpleDeptList()),
+ childrenField: 'children',
+ labelField: 'name',
+ placeholder: '璇烽�夋嫨浣跨敤閮ㄩ棬',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'chargeUserId',
+ label: '璐d换浜�',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨璐d换浜�',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'status',
+ label: '鍣ㄥ叿鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_DV_METERING_STATUS, 'number'),
+ placeholder: '璇烽�夋嫨鍣ㄥ叿鐘舵��',
+ },
+ },
+ {
+ fieldName: 'expireStatus',
+ label: '鍒版湡鐘舵��',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: METERING_EXPIRE_STATUS_OPTIONS,
+ placeholder: '璇烽�夋嫨鍒版湡鐘舵��',
+ },
+ help: '涓嬫妫�娴嬫棩鏈熻窛浠婂ぉ 鈮� 鎻愰啋澶╂暟锛堝熀纭�璁炬柦閰嶇疆 mes.dv-metering.remind-days锛夊嵆瑙嗕负銆屽嵆灏嗗埌鏈熴��',
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesDvMeteringApi.Metering>['columns'] {
+ return [
+ {
+ field: 'code',
+ title: '鍣ㄥ叿缂栫爜',
+ minWidth: 150,
+ slots: {
+ default: 'code',
+ },
+ },
+ { field: 'name', title: '鍣ㄥ叿鍚嶇О', minWidth: 150 },
+ {
+ field: 'category',
+ title: '鍣ㄥ叿绫诲埆',
+ width: 110,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_DV_METERING_CATEGORY },
+ },
+ },
+ { field: 'specification', title: '瑙勬牸鍨嬪彿', minWidth: 130 },
+ { field: 'brand', title: '鍝佺墝/鐢熶骇鍘傚', minWidth: 130 },
+ { field: 'deptName', title: '浣跨敤閮ㄩ棬', minWidth: 130 },
+ { field: 'chargeUserNickname', title: '璐d换浜�', minWidth: 100 },
+ {
+ field: 'expireStatus',
+ title: '鍒版湡鐘舵��',
+ width: 110,
+ slots: {
+ default: 'expireStatus',
+ },
+ },
+ {
+ field: 'nextCheckDate',
+ title: '涓嬫妫�娴嬫棩鏈�',
+ width: 130,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍣ㄥ叿鐘舵��',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_DV_METERING_STATUS },
+ },
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 210,
+ fixed: 'right',
+ slots: {
+ default: 'actions',
+ },
+ },
+ ];
+}
+
+/** 璁¢噺鍣ㄥ叿閫夋嫨寮圭獥鐨勬悳绱㈣〃鍗� */
+export function useMeteringSelectGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'code',
+ label: '鍣ㄥ叿缂栫爜',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ櫒鍏风紪鐮�',
+ },
+ },
+ {
+ fieldName: 'name',
+ label: '鍣ㄥ叿鍚嶇О',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ュ櫒鍏峰悕绉�',
+ },
+ },
+ ];
+}
+
+/** 璁¢噺鍣ㄥ叿閫夋嫨寮圭獥鐨勫瓧娈� */
+export function useMeteringSelectGridColumns(
+ multiple = false,
+): VxeTableGridOptions<MesDvMeteringApi.Metering>['columns'] {
+ return [
+ { type: multiple ? 'checkbox' : 'radio', width: 50 },
+ { field: 'code', title: '鍣ㄥ叿缂栫爜', width: 140 },
+ { field: 'name', title: '鍣ㄥ叿鍚嶇О', minWidth: 140 },
+ {
+ field: 'category',
+ title: '鍣ㄥ叿绫诲埆',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_DV_METERING_CATEGORY },
+ },
+ },
+ { field: 'specification', title: '瑙勬牸鍨嬪彿', minWidth: 120 },
+ { field: 'deptName', title: '浣跨敤閮ㄩ棬', width: 120 },
+ { field: 'chargeUserNickname', title: '璐d换浜�', width: 100 },
+ {
+ field: 'nextCheckDate',
+ title: '涓嬫妫�娴嬫棩鏈�',
+ width: 120,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'status',
+ title: '鍣ㄥ叿鐘舵��',
+ width: 90,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_DV_METERING_STATUS },
+ },
+ },
+ ];
+}
diff --git a/src/views/mes/dv/metering/index.vue b/src/views/mes/dv/metering/index.vue
new file mode 100644
index 0000000..27cbd3c
--- /dev/null
+++ b/src/views/mes/dv/metering/index.vue
@@ -0,0 +1,176 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesDvMeteringApi } from '#/api/mes/dv/metering';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+import { downloadFileFromBlobPart } from '@vben/utils';
+
+import { Button, message, Tag } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteMetering,
+ exportMetering,
+ getMeteringPage,
+} from '#/api/mes/dv/metering';
+import { $t } from '#/locales';
+
+import {
+ METERING_EXPIRE_STATUS_OPTIONS,
+ useGridColumns,
+ useGridFormSchema,
+} from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓璁¢噺鍣ㄥ叿 */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅璁¢噺鍣ㄥ叿 */
+function handleDetail(row: MesDvMeteringApi.Metering) {
+ formModalApi.setData({ id: row.id, formType: 'detail' }).open();
+}
+
+/** 缂栬緫璁¢噺鍣ㄥ叿 */
+function handleEdit(row: MesDvMeteringApi.Metering) {
+ formModalApi.setData({ id: row.id, formType: 'update' }).open();
+}
+
+/** 鍒犻櫎璁¢噺鍣ㄥ叿 */
+async function handleDelete(row: MesDvMeteringApi.Metering) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.name]),
+ duration: 0,
+ });
+ try {
+ await deleteMetering(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.name]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 瀵煎嚭璁¢噺鍣ㄥ叿 */
+async function handleExport() {
+ const data = await exportMetering(await gridApi.formApi.getValues());
+ downloadFileFromBlobPart({ fileName: '璁¢噺鍣ㄥ叿.xls', source: data });
+}
+
+/** 鍒版湡鐘舵�佸睍绀� */
+function getExpireStatusConfig(status?: number) {
+ return METERING_EXPIRE_STATUS_OPTIONS.find((item) => item.value === status);
+}
+
+const [Grid, gridApi] = useVbenVxeGrid({
+ formOptions: {
+ schema: useGridFormSchema(),
+ },
+ gridOptions: {
+ columns: useGridColumns(),
+ height: 'auto',
+ keepSource: true,
+ proxyConfig: {
+ ajax: {
+ query: async ({ page }, formValues) =>
+ await getMeteringPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ }),
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesDvMeteringApi.Metering>,
+});
+</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:dv-metering:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:dv-metering:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #code="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.code }}
+ </Button>
+ </template>
+ <template #expireStatus="{ row }">
+ <Tag
+ v-if="getExpireStatusConfig(row.expireStatus)"
+ :color="getExpireStatusConfig(row.expireStatus)!.color"
+ >
+ {{ getExpireStatusConfig(row.expireStatus)!.label }}
+ </Tag>
+ <span v-else>-</span>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:dv-metering:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:dv-metering:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.name]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: $t('common.detail'),
+ type: 'link',
+ icon: ACTION_ICON.VIEW,
+ auth: ['mes:dv-metering:query'],
+ onClick: handleDetail.bind(null, row),
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/mes/dv/metering/modules/form.vue b/src/views/mes/dv/metering/modules/form.vue
new file mode 100644
index 0000000..62535f4
--- /dev/null
+++ b/src/views/mes/dv/metering/modules/form.vue
@@ -0,0 +1,99 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesDvMeteringApi } from '#/api/mes/dv/metering';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createMetering,
+ getMetering,
+ updateMetering,
+} from '#/api/mes/dv/metering';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesDvMeteringApi.Metering>();
+const isDetail = computed(() => formType.value === 'detail');
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return '鏌ョ湅璁¢噺鍣ㄥ叿';
+ }
+ return formType.value === 'update' ? '淇敼璁¢噺鍣ㄥ叿' : '鏂板璁¢噺鍣ㄥ叿';
+});
+
+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 data = (await formApi.getValues()) as MesDvMeteringApi.Metering;
+ try {
+ if (formType.value === 'create') {
+ await createMetering(data);
+ } else {
+ await updateMetering(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(formType.value) });
+ formApi.setDisabled(formType.value === 'detail');
+ modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
+ if (!data?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getMetering(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>
diff --git a/src/views/mes/dv/meteringcheck/data.ts b/src/views/mes/dv/meteringcheck/data.ts
new file mode 100644
index 0000000..4e1e4e9
--- /dev/null
+++ b/src/views/mes/dv/meteringcheck/data.ts
@@ -0,0 +1,218 @@
+import type { VbenFormSchema } from '#/adapter/form';
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesDvMeteringCheckApi } from '#/api/mes/dv/meteringcheck';
+
+import { markRaw } from 'vue';
+
+import { DICT_TYPE } from '@vben/constants';
+import { getDictOptions } from '@vben/hooks';
+
+import { getSimpleUserList } from '#/api/system/user';
+import { DvMeteringSelect } from '#/views/mes/dv/metering/components';
+
+/** 琛ㄥ崟绫诲瀷 */
+export type FormType = 'create' | 'detail' | 'update';
+
+/** 鏂板/淇敼璁¢噺妫�娴嬭褰曠殑琛ㄥ崟 */
+export function useFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'id',
+ component: 'Input',
+ dependencies: {
+ triggerFields: [''],
+ show: () => false,
+ },
+ },
+ {
+ fieldName: 'meteringId',
+ label: '璁¢噺鍣ㄥ叿',
+ component: markRaw(DvMeteringSelect),
+ componentProps: {
+ placeholder: '璇烽�夋嫨璁¢噺鍣ㄥ叿',
+ },
+ help: '閫夋嫨鍣ㄥ叿鍚庯紝鍏舵牎鍑嗗懆鏈熷皢鐢ㄤ簬鑷姩璁$畻涓嬫妫�娴嬫棩鏈�',
+ rules: 'selectRequired',
+ },
+ {
+ fieldName: 'checkCode',
+ label: '妫�娴嬬紪鍙�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ娴嬬紪鍙�',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'checkDate',
+ label: '妫�娴嬫棩鏈�',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨妫�娴嬫棩鏈�',
+ valueFormat: 'YYYY-MM-DD',
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'checkOrg',
+ label: '妫�娴嬫満鏋�',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ユ娴嬫満鏋�',
+ },
+ },
+ {
+ fieldName: 'checkResult',
+ label: '妫�娴嬬粨璁�',
+ component: 'RadioGroup',
+ componentProps: {
+ buttonStyle: 'solid',
+ optionType: 'button',
+ options: getDictOptions(DICT_TYPE.MES_DV_METERING_CHECK_RESULT, 'number'),
+ },
+ rules: 'required',
+ },
+ {
+ fieldName: 'certNo',
+ label: '璇佷功缂栧彿',
+ component: 'Input',
+ componentProps: {
+ placeholder: '璇疯緭鍏ヨ瘉涔︾紪鍙�',
+ },
+ },
+ {
+ fieldName: 'checkUserId',
+ label: '妫�娴嬩汉',
+ component: 'ApiSelect',
+ componentProps: {
+ allowClear: true,
+ api: getSimpleUserList,
+ labelField: 'nickname',
+ placeholder: '璇烽�夋嫨妫�娴嬩汉',
+ valueField: 'id',
+ },
+ },
+ {
+ fieldName: 'nextCheckDate',
+ label: '涓嬫妫�娴嬫棩鏈�',
+ component: 'DatePicker',
+ componentProps: {
+ class: '!w-full',
+ format: 'YYYY-MM-DD',
+ placeholder: '璇烽�夋嫨涓嬫妫�娴嬫棩鏈�',
+ valueFormat: 'YYYY-MM-DD',
+ },
+ help: '鍒版湡鏃ワ紝寤鸿涓嶅~锛氬皢鎸夈�屾娴嬫棩鏈� + 鏍″噯鍛ㄦ湡銆嶈嚜鍔ㄨ绠楀苟鍥炲啓鍒板櫒鍏凤紱涔熷彲鎵嬪姩鎸囧畾',
+ },
+ {
+ fieldName: 'remark',
+ label: '澶囨敞',
+ component: 'Textarea',
+ formItemClass: 'col-span-3',
+ componentProps: {
+ placeholder: '璇疯緭鍏ュ娉�',
+ rows: 3,
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勬悳绱㈣〃鍗� */
+export function useGridFormSchema(): VbenFormSchema[] {
+ return [
+ {
+ fieldName: 'meteringId',
+ label: '璁¢噺鍣ㄥ叿',
+ component: markRaw(DvMeteringSelect),
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇烽�夋嫨璁¢噺鍣ㄥ叿',
+ },
+ },
+ {
+ fieldName: 'checkCode',
+ label: '妫�娴嬬紪鍙�',
+ component: 'Input',
+ componentProps: {
+ allowClear: true,
+ placeholder: '璇疯緭鍏ユ娴嬬紪鍙�',
+ },
+ },
+ {
+ fieldName: 'checkResult',
+ label: '妫�娴嬬粨璁�',
+ component: 'Select',
+ componentProps: {
+ allowClear: true,
+ options: getDictOptions(DICT_TYPE.MES_DV_METERING_CHECK_RESULT, 'number'),
+ placeholder: '璇烽�夋嫨妫�娴嬬粨璁�',
+ },
+ },
+ {
+ fieldName: 'checkDate',
+ label: '妫�娴嬫棩鏈�',
+ component: 'RangePicker',
+ componentProps: {
+ allowClear: true,
+ format: 'YYYY-MM-DD',
+ valueFormat: 'YYYY-MM-DD',
+ },
+ },
+ ];
+}
+
+/** 鍒楄〃鐨勫瓧娈� */
+export function useGridColumns(): VxeTableGridOptions<MesDvMeteringCheckApi.MeteringCheck>['columns'] {
+ return [
+ {
+ field: 'meteringCode',
+ title: '鍣ㄥ叿缂栫爜',
+ minWidth: 140,
+ slots: {
+ default: 'meteringCode',
+ },
+ },
+ { field: 'meteringName', title: '鍣ㄥ叿鍚嶇О', minWidth: 150 },
+ { field: 'checkCode', title: '妫�娴嬬紪鍙�', minWidth: 140 },
+ {
+ field: 'checkDate',
+ title: '妫�娴嬫棩鏈�',
+ width: 120,
+ formatter: 'formatDate',
+ },
+ { field: 'checkOrg', title: '妫�娴嬫満鏋�', minWidth: 140 },
+ {
+ field: 'checkResult',
+ title: '妫�娴嬬粨璁�',
+ width: 100,
+ cellRender: {
+ name: 'CellDict',
+ props: { type: DICT_TYPE.MES_DV_METERING_CHECK_RESULT },
+ },
+ },
+ { field: 'certNo', title: '璇佷功缂栧彿', minWidth: 130 },
+ { field: 'checkUserNickname', title: '妫�娴嬩汉', minWidth: 100 },
+ {
+ field: 'nextCheckDate',
+ title: '涓嬫妫�娴嬫棩鏈�',
+ width: 130,
+ formatter: 'formatDate',
+ },
+ {
+ field: 'createTime',
+ title: '鍒涘缓鏃堕棿',
+ width: 180,
+ formatter: 'formatDateTime',
+ },
+ {
+ title: '鎿嶄綔',
+ width: 200,
+ fixed: 'right',
+ slots: {
+ default: 'actions',
+ },
+ },
+ ];
+}
diff --git a/src/views/mes/dv/meteringcheck/index.vue b/src/views/mes/dv/meteringcheck/index.vue
new file mode 100644
index 0000000..c8039f3
--- /dev/null
+++ b/src/views/mes/dv/meteringcheck/index.vue
@@ -0,0 +1,158 @@
+<script lang="ts" setup>
+import type { VxeTableGridOptions } from '#/adapter/vxe-table';
+import type { MesDvMeteringCheckApi } from '#/api/mes/dv/meteringcheck';
+
+import { Page, useVbenModal } from '@vben/common-ui';
+import { downloadFileFromBlobPart } from '@vben/utils';
+
+import { Button, message } from 'ant-design-vue';
+
+import { ACTION_ICON, TableAction, useVbenVxeGrid } from '#/adapter/vxe-table';
+import {
+ deleteMeteringCheck,
+ exportMeteringCheck,
+ getMeteringCheckPage,
+} from '#/api/mes/dv/meteringcheck';
+import { $t } from '#/locales';
+
+import { useGridColumns, useGridFormSchema } from './data';
+import Form from './modules/form.vue';
+
+const [FormModal, formModalApi] = useVbenModal({
+ connectedComponent: Form,
+ destroyOnClose: true,
+});
+
+/** 鍒锋柊琛ㄦ牸 */
+function handleRefresh() {
+ gridApi.query();
+}
+
+/** 鍒涘缓璁¢噺妫�娴嬭褰� */
+function handleCreate() {
+ formModalApi.setData({ formType: 'create' }).open();
+}
+
+/** 鏌ョ湅璁¢噺妫�娴嬭褰� */
+function handleDetail(row: MesDvMeteringCheckApi.MeteringCheck) {
+ formModalApi.setData({ id: row.id, formType: 'detail' }).open();
+}
+
+/** 缂栬緫璁¢噺妫�娴嬭褰� */
+function handleEdit(row: MesDvMeteringCheckApi.MeteringCheck) {
+ formModalApi.setData({ id: row.id, formType: 'update' }).open();
+}
+
+/** 鍒犻櫎璁¢噺妫�娴嬭褰� */
+async function handleDelete(row: MesDvMeteringCheckApi.MeteringCheck) {
+ const hideLoading = message.loading({
+ content: $t('ui.actionMessage.deleting', [row.checkCode]),
+ duration: 0,
+ });
+ try {
+ await deleteMeteringCheck(row.id!);
+ message.success($t('ui.actionMessage.deleteSuccess', [row.checkCode]));
+ handleRefresh();
+ } finally {
+ hideLoading();
+ }
+}
+
+/** 瀵煎嚭璁¢噺妫�娴嬭褰� */
+async function handleExport() {
+ const data = await exportMeteringCheck(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) =>
+ await getMeteringCheckPage({
+ pageNo: page.currentPage,
+ pageSize: page.pageSize,
+ ...formValues,
+ }),
+ },
+ },
+ rowConfig: {
+ keyField: 'id',
+ isHover: true,
+ },
+ toolbarConfig: {
+ refresh: true,
+ search: true,
+ },
+ } as VxeTableGridOptions<MesDvMeteringCheckApi.MeteringCheck>,
+});
+</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:dv-metering-check:create'],
+ onClick: handleCreate,
+ },
+ {
+ label: $t('ui.actionTitle.export'),
+ type: 'primary',
+ icon: ACTION_ICON.DOWNLOAD,
+ auth: ['mes:dv-metering-check:export'],
+ onClick: handleExport,
+ },
+ ]"
+ />
+ </template>
+ <template #meteringCode="{ row }">
+ <Button type="link" @click="handleDetail(row)">
+ {{ row.meteringCode }}
+ </Button>
+ </template>
+ <template #actions="{ row }">
+ <TableAction
+ :actions="[
+ {
+ label: $t('common.edit'),
+ type: 'link',
+ icon: ACTION_ICON.EDIT,
+ auth: ['mes:dv-metering-check:update'],
+ onClick: handleEdit.bind(null, row),
+ },
+ {
+ label: $t('common.delete'),
+ type: 'link',
+ danger: true,
+ icon: ACTION_ICON.DELETE,
+ auth: ['mes:dv-metering-check:delete'],
+ popConfirm: {
+ title: $t('ui.actionMessage.deleteConfirm', [row.checkCode]),
+ confirm: handleDelete.bind(null, row),
+ },
+ },
+ {
+ label: $t('common.detail'),
+ type: 'link',
+ icon: ACTION_ICON.VIEW,
+ auth: ['mes:dv-metering-check:query'],
+ onClick: handleDetail.bind(null, row),
+ },
+ ]"
+ />
+ </template>
+ </Grid>
+ </Page>
+</template>
diff --git a/src/views/mes/dv/meteringcheck/modules/form.vue b/src/views/mes/dv/meteringcheck/modules/form.vue
new file mode 100644
index 0000000..308d312
--- /dev/null
+++ b/src/views/mes/dv/meteringcheck/modules/form.vue
@@ -0,0 +1,99 @@
+<script lang="ts" setup>
+import type { FormType } from '../data';
+
+import type { MesDvMeteringCheckApi } from '#/api/mes/dv/meteringcheck';
+
+import { computed, ref } from 'vue';
+
+import { useVbenModal } from '@vben/common-ui';
+
+import { message } from 'ant-design-vue';
+
+import { useVbenForm } from '#/adapter/form';
+import {
+ createMeteringCheck,
+ getMeteringCheck,
+ updateMeteringCheck,
+} from '#/api/mes/dv/meteringcheck';
+import { $t } from '#/locales';
+
+import { useFormSchema } from '../data';
+
+const emit = defineEmits(['success']);
+const formType = ref<FormType>('create');
+const formData = ref<MesDvMeteringCheckApi.MeteringCheck>();
+const isDetail = computed(() => formType.value === 'detail');
+const getTitle = computed(() => {
+ if (formType.value === 'detail') {
+ return '鏌ョ湅璁¢噺妫�娴嬭褰�';
+ }
+ return formType.value === 'update' ? '淇敼璁¢噺妫�娴嬭褰�' : '鏂板璁¢噺妫�娴嬭褰�';
+});
+
+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 data = (await formApi.getValues()) as MesDvMeteringCheckApi.MeteringCheck;
+ try {
+ if (formType.value === 'create') {
+ await createMeteringCheck(data);
+ } else {
+ await updateMeteringCheck(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() });
+ formApi.setDisabled(formType.value === 'detail');
+ modalApi.setState({ showConfirmButton: formType.value !== 'detail' });
+ if (!data?.id) {
+ return;
+ }
+ modalApi.lock();
+ try {
+ formData.value = await getMeteringCheck(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>
--
Gitblit v1.9.3