From acd01127d1c2d630758fd0ebcc3f582d0dbf793c Mon Sep 17 00:00:00 2001 From: caiyuchao Date: Mon, 30 Jun 2025 19:03:10 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E9=A1=B9=E7=9B=AE=E7=AE=A1=E7=90=86?= =?UTF-8?q?=E8=B0=83=E6=95=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../web-antd/src/api/license/project/index.ts | 29 +- .../src/locales/langs/en-US/project.json | 22 ++ .../src/locales/langs/zh-CN/project.json | 22 ++ .../src/views/license/project/data.ts | 279 ++++++++++-------- .../src/views/license/project/index.vue | 11 +- .../views/license/project/modules/form.vue | 22 +- 6 files changed, 256 insertions(+), 129 deletions(-) create mode 100644 apps/web-antd/src/locales/langs/en-US/project.json create mode 100644 apps/web-antd/src/locales/langs/zh-CN/project.json diff --git a/apps/web-antd/src/api/license/project/index.ts b/apps/web-antd/src/api/license/project/index.ts index 21855da..c90733b 100644 --- a/apps/web-antd/src/api/license/project/index.ts +++ b/apps/web-antd/src/api/license/project/index.ts @@ -8,13 +8,13 @@ export namespace ProjectApi { /** 项目信息 */ export interface Project { id: number; // 主键 - customerId?: number; // 客户ID + projectId?: number; // 项目ID name?: string; // 项目名称 code?: string; // 项目编号 contractCode?: string; // 合同编号 businessStatus: number; // 商务状态 businessOwner?: number; // 业务负责人 - customerOwner?: number; // 客户对接人 + projectOwner?: number; // 项目对接人 technicalOwnerA?: number; // 技术负责人1 technicalOwnerB: number; // 技术负责人2 technicalOwnerC: number; // 技术负责人3 @@ -59,3 +59,28 @@ export function deleteProject(id: number) { export function exportProject(params: any) { return requestClient.download('/license/project/export-excel', params); } + +/** 项目名称是否唯一 */ +export async function isProjectNameUnique( + name: string, + id?: ProjectApi.Project['id'], +) { + return requestClient.get('/license/project/name-unique', { + params: { id, name }, + }); +} + +/** 项目编号是否唯一 */ +export async function isProjectCodeUnique( + code: number, + id?: ProjectApi.Project['id'], +) { + return requestClient.get('/license/project/code-unique', { + params: { id, code }, + }); +} + +/** 查询当前最大sn */ +export async function getMaxSn() { + return requestClient.get('/license/project/max-sn'); +} diff --git a/apps/web-antd/src/locales/langs/en-US/project.json b/apps/web-antd/src/locales/langs/en-US/project.json new file mode 100644 index 0000000..8a65a0c --- /dev/null +++ b/apps/web-antd/src/locales/langs/en-US/project.json @@ -0,0 +1,22 @@ +{ + "project": "Project", + "operation": "Operation", + "creationTime": "Creation Time", + "remarks": "Remarks", + "envInfo": "Environment Info", + "status": "Project Status", + "endTime": "Project End Time", + "startTime": "Project Start Time", + "technicalOwnerC": "Technical Owner 3", + "technicalOwnerB": "Technical Owner 2", + "technicalOwnerA": "Technical Owner 1", + "customerOwner": "Customer Contact", + "businessOwner": "Business Owner", + "businessStatus": "Business Status", + "contractCode": "Contract Code", + "belongCustomer": "Belonging Customer", + "code": "Project Code", + "name": "Project Name", + "envInfoFile": "Environment Info Attachment", + "list": "Project List" +} diff --git a/apps/web-antd/src/locales/langs/zh-CN/project.json b/apps/web-antd/src/locales/langs/zh-CN/project.json new file mode 100644 index 0000000..0a96a65 --- /dev/null +++ b/apps/web-antd/src/locales/langs/zh-CN/project.json @@ -0,0 +1,22 @@ +{ + "project": "项目", + "operation": "操作", + "creationTime": "创建时间", + "remarks": "备注", + "envInfo": "环境信息", + "status": "项目状态", + "endTime": "项目结束时间", + "startTime": "项目开始时间", + "technicalOwnerC": "技术负责人3", + "technicalOwnerB": "技术负责人2", + "technicalOwnerA": "技术负责人1", + "customerOwner": "客户对接人", + "businessOwner": "业务负责人", + "businessStatus": "商务状态", + "contractCode": "合同编号", + "belongCustomer": "所属客户", + "code": "项目编号", + "name": "项目名称", + "envInfoFile": "环境信息附件", + "list": "项目列表" +} diff --git a/apps/web-antd/src/views/license/project/data.ts b/apps/web-antd/src/views/license/project/data.ts index 7b53e2b..ce610c6 100644 --- a/apps/web-antd/src/views/license/project/data.ts +++ b/apps/web-antd/src/views/license/project/data.ts @@ -2,14 +2,25 @@ import type { VbenFormSchema } from '#/adapter/form'; import type { OnActionClickFn, VxeTableGridOptions } from '#/adapter/vxe-table'; import type { ProjectApi } from '#/api/license/project'; +import { ref } from 'vue'; + import { useAccess } from '@vben/access'; +import { z } from '#/adapter/form'; import { getCustomerList } from '#/api/license/customer'; +import { + isProjectCodeUnique, + isProjectNameUnique, +} from '#/api/license/project'; import { getSimpleUserList } from '#/api/system/user'; +import { $t } from '#/locales'; import { DICT_TYPE, getDictOptions, getRangePickerDefaultProps } from '#/utils'; const { hasAccessByCodes } = useAccess(); +const userList = await getSimpleUserList(); + +export const formData = ref(); /** 新增/修改的表单 */ export function useFormSchema(): VbenFormSchema[] { return [ @@ -23,25 +34,54 @@ export function useFormSchema(): VbenFormSchema[] { }, { fieldName: 'name', - label: '项目名称', - rules: 'required', + label: $t('project.name'), component: 'Input', - componentProps: { - placeholder: '请输入项目名称', - }, + rules: z + .string() + .min(1, $t('ui.formRules.required', [$t('project.name')])) + .max(60, $t('ui.formRules.maxLength', [$t('project.name'), 60])) + .refine( + async (value: string) => { + return await isProjectNameUnique(value, formData.value?.id); + }, + (value) => ({ + message: $t('ui.formRules.alreadyExists', [ + $t('project.name'), + value, + ]), + }), + ), }, { fieldName: 'code', - label: '项目编号', - rules: 'required', - component: 'Input', - componentProps: { - placeholder: '请输入项目编号', - }, + label: $t('project.code'), + component: 'InputNumber', + rules: z + .number() + .min(2000, $t('ui.formRules.range', [$t('project.code'), 2000, 9999])) + .max(9999, $t('ui.formRules.range', [$t('project.code'), 2000, 9999])) + .refine( + async (value: number) => { + return await isProjectCodeUnique(value, formData.value?.id); + }, + (value) => ({ + message: $t('ui.formRules.alreadyExists', [ + $t('project.code'), + value, + ]), + }), + ) + .nullable() + .refine( + (value: null | number) => { + return value; + }, + { message: $t('ui.formRules.required', [$t('project.code')]) }, + ), }, { fieldName: 'customerId', - label: '所属客户', + label: $t('project.belongCustomer'), rules: 'required', component: 'ApiSelect', componentProps: { @@ -60,104 +100,104 @@ export function useFormSchema(): VbenFormSchema[] { }, { fieldName: 'contractCode', - label: '合同编号', + label: $t('project.contractCode'), rules: 'required', component: 'Input', - componentProps: { - placeholder: '请输入合同编号', - }, }, { fieldName: 'businessStatus', - label: '商务状态', + label: $t('project.businessStatus'), rules: 'required', component: 'Select', componentProps: { options: getDictOptions(DICT_TYPE.LIC_BUSINESS_STATUS, 'number'), - placeholder: '请选择商务状态', }, }, { fieldName: 'status', - label: '项目状态', + label: $t('project.status'), rules: 'required', component: 'Select', componentProps: { options: getDictOptions(DICT_TYPE.LIC_PROJECT_STATUS, 'number'), - placeholder: '请选择项目状态', }, }, { fieldName: 'businessOwner', - label: '业务负责人', + label: $t('project.businessOwner'), rules: 'required', - component: 'ApiSelect', + component: 'Select', componentProps: { - api: getSimpleUserList, - labelField: 'nickname', - valueField: 'id', + options: userList, + allowClear: true, showSearch: true, - filterOption: (input: string, option: any) => - option.label.toLowerCase().includes(input.toLowerCase()), + fieldNames: { label: 'nickname', value: 'id' }, + filterOption: (input: string, option: any) => { + return option.nickname.toLowerCase().includes(input.toLowerCase()); + }, }, }, { fieldName: 'customerOwner', - label: '客户对接人', + label: $t('project.customerOwner'), rules: 'required', - component: 'ApiSelect', + component: 'Select', componentProps: { - api: getSimpleUserList, - labelField: 'nickname', - valueField: 'id', + options: userList, + allowClear: true, showSearch: true, - filterOption: (input: string, option: any) => - option.label.toLowerCase().includes(input.toLowerCase()), + fieldNames: { label: 'nickname', value: 'id' }, + filterOption: (input: string, option: any) => { + return option.nickname.toLowerCase().includes(input.toLowerCase()); + }, }, }, { fieldName: 'technicalOwnerA', - label: '技术负责人1', + label: $t('project.technicalOwnerA'), rules: 'required', - component: 'ApiSelect', + component: 'Select', componentProps: { - api: getSimpleUserList, - labelField: 'nickname', - valueField: 'id', + options: userList, + allowClear: true, showSearch: true, - filterOption: (input: string, option: any) => - option.label.toLowerCase().includes(input.toLowerCase()), + fieldNames: { label: 'nickname', value: 'id' }, + filterOption: (input: string, option: any) => { + return option.nickname.toLowerCase().includes(input.toLowerCase()); + }, }, }, { fieldName: 'technicalOwnerB', - label: '技术负责人2', - component: 'ApiSelect', + label: $t('project.technicalOwnerB'), + component: 'Select', componentProps: { - api: getSimpleUserList, - labelField: 'nickname', - valueField: 'id', + options: userList, + allowClear: true, showSearch: true, - filterOption: (input: string, option: any) => - option.label.toLowerCase().includes(input.toLowerCase()), + fieldNames: { label: 'nickname', value: 'id' }, + filterOption: (input: string, option: any) => { + return option.nickname.toLowerCase().includes(input.toLowerCase()); + }, }, }, { fieldName: 'technicalOwnerC', - label: '技术负责人3', - component: 'ApiSelect', + label: $t('project.technicalOwnerC'), + component: 'Select', componentProps: { - api: getSimpleUserList, - labelField: 'nickname', - valueField: 'id', + options: userList, + allowClear: true, showSearch: true, - filterOption: (input: string, option: any) => - option.label.toLowerCase().includes(input.toLowerCase()), + fieldNames: { label: 'nickname', value: 'id' }, + filterOption: (input: string, option: any) => { + return option.nickname.toLowerCase().includes(input.toLowerCase()); + }, }, }, { fieldName: 'startTime', - label: '项目开始时间', + label: $t('project.startTime'), component: 'DatePicker', componentProps: { showTime: true, @@ -167,7 +207,7 @@ export function useFormSchema(): VbenFormSchema[] { }, { fieldName: 'endTime', - label: '项目结束时间', + label: $t('project.endTime'), component: 'DatePicker', componentProps: { showTime: true, @@ -177,27 +217,18 @@ export function useFormSchema(): VbenFormSchema[] { }, { fieldName: 'envInfo', - label: '环境信息', + label: $t('project.envInfo'), component: 'Input', - componentProps: { - placeholder: '请输入环境信息', - }, }, { fieldName: 'envFileId', - label: '环境信息附件id', + label: $t('project.envInfoFile'), component: 'Input', - componentProps: { - placeholder: '请输入环境信息附件id', - }, }, { fieldName: 'remark', - label: '备注', + label: $t('project.remarks'), component: 'Textarea', - componentProps: { - placeholder: '请输入备注', - }, }, ]; } @@ -207,94 +238,110 @@ export function useGridFormSchema(): VbenFormSchema[] { return [ { fieldName: 'name', - label: '项目名称', + label: $t('project.name'), component: 'Input', componentProps: { allowClear: true, - placeholder: '请输入项目名称', }, }, { fieldName: 'code', - label: '项目编号', + label: $t('project.code'), component: 'Input', componentProps: { allowClear: true, - placeholder: '请输入项目编号', }, }, { fieldName: 'contractCode', - label: '合同编号', + label: $t('project.contractCode'), component: 'Input', componentProps: { allowClear: true, - placeholder: '请输入合同编号', }, }, { fieldName: 'businessStatus', - label: '商务状态', + label: $t('project.businessStatus'), component: 'Select', componentProps: { allowClear: true, options: getDictOptions(DICT_TYPE.LIC_BUSINESS_STATUS, 'number'), - placeholder: '请选择商务状态', }, }, { fieldName: 'businessOwner', - label: '业务负责人', + label: $t('project.businessOwner'), component: 'Select', componentProps: { + options: userList, allowClear: true, - options: [], - placeholder: '请选择业务负责人', + showSearch: true, + fieldNames: { label: 'nickname', value: 'id' }, + filterOption: (input: string, option: any) => { + return option.nickname.toLowerCase().includes(input.toLowerCase()); + }, }, }, { fieldName: 'customerOwner', - label: '客户对接人', + label: $t('project.customerOwner'), component: 'Select', componentProps: { + options: userList, allowClear: true, - options: [], - placeholder: '请选择客户对接人', + showSearch: true, + fieldNames: { label: 'nickname', value: 'id' }, + filterOption: (input: string, option: any) => { + return option.nickname.toLowerCase().includes(input.toLowerCase()); + }, }, }, { fieldName: 'technicalOwnerA', - label: '技术负责人1', + label: $t('project.technicalOwnerA'), component: 'Select', componentProps: { + options: userList, allowClear: true, - options: [], - placeholder: '请选择技术负责人1', + showSearch: true, + fieldNames: { label: 'nickname', value: 'id' }, + filterOption: (input: string, option: any) => { + return option.nickname.toLowerCase().includes(input.toLowerCase()); + }, }, }, { fieldName: 'technicalOwnerB', - label: '技术负责人2', + label: $t('project.technicalOwnerB'), component: 'Select', componentProps: { + options: userList, allowClear: true, - options: [], - placeholder: '请选择技术负责人2', + showSearch: true, + fieldNames: { label: 'nickname', value: 'id' }, + filterOption: (input: string, option: any) => { + return option.nickname.toLowerCase().includes(input.toLowerCase()); + }, }, }, { fieldName: 'technicalOwnerC', - label: '技术负责人3', + label: $t('project.technicalOwnerC'), component: 'Select', componentProps: { + options: userList, allowClear: true, - options: [], - placeholder: '请选择技术负责人3', + showSearch: true, + fieldNames: { label: 'nickname', value: 'id' }, + filterOption: (input: string, option: any) => { + return option.nickname.toLowerCase().includes(input.toLowerCase()); + }, }, }, { fieldName: 'startTime', - label: '项目开始时间', + label: $t('project.startTime'), component: 'RangePicker', componentProps: { ...getRangePickerDefaultProps(), @@ -303,7 +350,7 @@ export function useGridFormSchema(): VbenFormSchema[] { }, { fieldName: 'endTime', - label: '项目结束时间', + label: $t('project.endTime'), component: 'RangePicker', componentProps: { ...getRangePickerDefaultProps(), @@ -312,26 +359,24 @@ export function useGridFormSchema(): VbenFormSchema[] { }, { fieldName: 'status', - label: '项目状态', + label: $t('project.status'), component: 'Select', componentProps: { allowClear: true, options: getDictOptions(DICT_TYPE.LIC_PROJECT_STATUS, 'number'), - placeholder: '请选择项目状态', }, }, { fieldName: 'envInfo', - label: '环境信息', + label: $t('project.envInfo'), component: 'Input', componentProps: { allowClear: true, - placeholder: '请输入环境信息', }, }, { fieldName: 'createTime', - label: '创建时间', + label: $t('project.creationTime'), component: 'RangePicker', componentProps: { ...getRangePickerDefaultProps(), @@ -348,27 +393,27 @@ export function useGridColumns( return [ { field: 'name', - title: '项目名称', + title: $t('project.name'), minWidth: 120, }, { field: 'code', - title: '项目编号', + title: $t('project.code'), minWidth: 120, }, { field: 'customerName', - title: '所属客户', + title: $t('project.belongCustomer'), minWidth: 120, }, { field: 'contractCode', - title: '合同编号', + title: $t('project.contractCode'), minWidth: 120, }, { field: 'businessStatus', - title: '商务状态', + title: $t('project.businessStatus'), minWidth: 120, cellRender: { name: 'CellDict', @@ -377,44 +422,44 @@ export function useGridColumns( }, { field: 'businessOwnerName', - title: '业务负责人', + title: $t('project.businessOwner'), minWidth: 120, }, { field: 'customerOwnerName', - title: '客户对接人', + title: $t('project.customerOwner'), minWidth: 120, }, { field: 'technicalOwnerAName', - title: '技术负责人1', + title: $t('project.technicalOwnerA'), minWidth: 120, }, { field: 'technicalOwnerBName', - title: '技术负责人2', + title: $t('project.technicalOwnerB'), minWidth: 120, }, { field: 'technicalOwnerCName', - title: '技术负责人3', + title: $t('project.technicalOwnerC'), minWidth: 120, }, { field: 'startTime', - title: '项目开始时间', + title: $t('project.startTime'), minWidth: 120, formatter: 'formatDateTime', }, { field: 'endTime', - title: '项目结束时间', + title: $t('project.endTime'), minWidth: 120, formatter: 'formatDateTime', }, { field: 'status', - title: '项目状态', + title: $t('project.status'), minWidth: 120, cellRender: { name: 'CellDict', @@ -423,23 +468,23 @@ export function useGridColumns( }, { field: 'envInfo', - title: '环境信息', + title: $t('project.envInfo'), minWidth: 120, }, { field: 'remark', - title: '备注', + title: $t('project.remarks'), minWidth: 120, }, { field: 'createTime', - title: '创建时间', + title: $t('project.creationTime'), minWidth: 120, formatter: 'formatDateTime', }, { field: 'operation', - title: '操作', + title: $t('project.operation'), minWidth: 200, align: 'center', fixed: 'right', @@ -448,7 +493,7 @@ export function useGridColumns( cellRender: { attrs: { nameField: 'id', - nameTitle: '项目', + nameTitle: $t('project.project'), onClick: onActionClick, }, name: 'CellOperation', diff --git a/apps/web-antd/src/views/license/project/index.vue b/apps/web-antd/src/views/license/project/index.vue index c46ff9e..514b0f0 100644 --- a/apps/web-antd/src/views/license/project/index.vue +++ b/apps/web-antd/src/views/license/project/index.vue @@ -63,7 +63,10 @@ async function onDelete(row: ProjectApi.Project) { /** 导出表格 */ async function onExport() { const data = await exportProject(await gridApi.formApi.getValues()); - downloadFileFromBlobPart({ fileName: '项目.xls', source: data }); + downloadFileFromBlobPart({ + fileName: `${$t('project.project')}.xls`, + source: data, + }); } /** 表格操作按钮的回调函数 */ @@ -82,6 +85,8 @@ function onActionClick({ code, row }: OnActionClickParams) { const [Grid, gridApi] = useVbenVxeGrid({ formOptions: { + collapsed: true, + collapsedRows: 2, schema: useGridFormSchema(), }, gridOptions: { @@ -117,7 +122,7 @@ const [Grid, gridApi] = useVbenVxeGrid({ - +