feat(resource): 新增资源管理模块及相关接口和界面
Some checks failed
Lint Code / Lint Code (push) Failing after 3m1s

- 定义资源实体类型及相关请求参数类型
- 实现资源增删改查及状态、数量更新接口
- 添加资源预算汇总、待审批和即将到位资源查询接口
- 在项目详情页增加资源权限控制相关计算属性
- 实现资源编辑模态框及新增、编辑、保存、删除功能
- 资源列表新增操作列支持资源编辑和删除权限控制
- 在界面中显示资源数量及新增资源按钮
- 移除冗余操作按钮,统一资源操作权限管理
This commit is contained in:
2026-03-31 17:39:21 +08:00
parent 9c777ee429
commit 93ea80a636
3 changed files with 1427 additions and 8 deletions

View File

@@ -605,3 +605,148 @@ export const getTaskStatusStats = (projectId: string) => {
{ params: { projectId } }
);
};
// ==================== 资源管理 API ====================
/** 资源实体 - 根据 OpenAPI 定义 */
export type Resource = {
id?: string;
resourceCode?: string;
projectId?: string;
resourceType?: string; // human-人力, material-物料, equipment-设备, software-软件, finance-资金, other-其他
resourceName?: string;
description?: string;
specification?: string;
unit?: string;
planQuantity?: number;
actualQuantity?: number;
unitPrice?: number;
currency?: string;
supplier?: string;
status?: string; // planned-计划中, requested-已申请, approved-已批准, procuring-采购中, arrived-已到货, in_use-使用中, completed-已完成
planArriveDate?: string;
actualArriveDate?: string;
responsibleId?: string;
responsibleName?: string;
location?: string;
tags?: string[];
extraData?: Record<string, any>;
createBy?: string;
createTime?: string;
updateBy?: string;
updateTime?: string;
};
/** 资源更新请求 - 根据 OpenAPI 定义 */
export type ResourceUpdateRequest = {
id: string;
resourceType?: string;
resourceName?: string;
description?: string;
specification?: string;
unit?: string;
planQuantity?: number;
actualQuantity?: number;
unitPrice?: number;
currency?: string;
supplier?: string;
status?: string;
planArriveDate?: string;
actualArriveDate?: string;
responsibleId?: string;
responsibleName?: string;
location?: string;
tags?: string[];
extraData?: Record<string, any>;
};
/** 资源查询参数 */
export type ResourceQueryParams = {
pageNum?: number;
pageSize?: number;
projectId?: string;
resourceType?: string;
status?: string;
keyword?: string;
};
/** 分页数据结构(资源列表用) */
export type PageResult<T> = {
records: T[];
total: number;
size: number;
current: number;
};
/** 分页查询资源列表 */
export const getResourceList = (params?: ResourceQueryParams) => {
return http.request<Result<PageResult<Resource>>>(
"get",
"/api/v1/resource/list",
{ params }
);
};
/** 根据ID查询资源详情 */
export const getResourceById = (id: string) => {
return http.request<Result<Resource>>("get", `/api/v1/resource/${id}`);
};
/** 新增资源 */
export const createResource = (data: Resource) => {
return http.request<Result<string>>("post", "/api/v1/resource", { data });
};
/** 修改资源 */
export const updateResource = (data: ResourceUpdateRequest) => {
return http.request<Result<void>>("put", "/api/v1/resource", { data });
};
/** 删除资源 */
export const deleteResource = (id: string) => {
return http.request<Result<void>>("delete", `/api/v1/resource/${id}`);
};
/** 更新资源状态 */
export const updateResourceStatus = (id: string, status: string) => {
return http.request<Result<void>>("put", `/api/v1/resource/${id}/status`, {
params: { status }
});
};
/** 更新资源数量 */
export const updateResourceQuantity = (id: string, actualQuantity: string) => {
return http.request<Result<void>>("put", `/api/v1/resource/${id}/quantity`, {
params: { actualQuantity }
});
};
/** 查询资源预算汇总 */
export const getResourceBudgetStats = (projectId: string) => {
return http.request<Result<Record<string, any>[]>>(
"get",
"/api/v1/resource/stats/budget",
{ params: { projectId } }
);
};
/** 查询即将到位的资源 */
export const getPendingArrivalResources = (
projectId: string,
days: number = 7
) => {
return http.request<Result<Resource[]>>(
"get",
"/api/v1/resource/pending-arrival",
{ params: { projectId, days } }
);
};
/** 查询待审批的资源申请 */
export const getPendingApprovalResources = (projectId?: string) => {
return http.request<Result<Record<string, any>[]>>(
"get",
"/api/v1/resource/pending-approval",
{ params: { projectId } }
);
};

View File

@@ -0,0 +1,917 @@
{
"openapi": "3.0.1",
"info": {
"title": "默认模块",
"description": "",
"version": "1.0.0"
},
"tags": [],
"paths": {
"/api/v1/resource/list": {
"get": {
"summary": "分页查询资源列表",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "pageNum",
"in": "query",
"description": "",
"required": true,
"example": 1,
"schema": {
"type": "integer"
}
},
{
"name": "pageSize",
"in": "query",
"description": "",
"required": true,
"example": 10,
"schema": {
"type": "integer"
}
},
{
"name": "projectId",
"in": "query",
"description": "",
"required": false,
"schema": {
"type": "integer"
}
},
{
"name": "resourceType",
"in": "query",
"description": "",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "status",
"in": "query",
"description": "",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "keyword",
"in": "query",
"description": "",
"required": false,
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
"schema": {
"type": "string",
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BaseResponsePageMapObject"
}
}
}
}
},
"security": []
}
},
"/api/v1/resource/{id}": {
"get": {
"summary": "根据ID查询资源详情",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "id",
"in": "path",
"description": "",
"required": true,
"schema": {
"type": "integer"
}
},
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
"schema": {
"type": "string",
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BaseResponseResource"
}
}
}
}
},
"security": []
},
"delete": {
"summary": "删除资源",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "id",
"in": "path",
"description": "",
"required": true,
"schema": {
"type": "integer"
}
},
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
"schema": {
"type": "string",
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BaseResponseVoid"
}
}
}
}
},
"security": []
}
},
"/api/v1/resource": {
"post": {
"summary": "新增资源",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
"schema": {
"type": "string",
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/Resource",
"description": ""
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BaseResponseLong"
}
}
}
}
},
"security": []
},
"put": {
"summary": "修改资源",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
"schema": {
"type": "string",
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
}
}
],
"requestBody": {
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/ResourceUpdateRequest",
"description": ""
}
}
}
},
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BaseResponseVoid"
}
}
}
}
},
"security": []
}
},
"/api/v1/resource/{id}/status": {
"put": {
"summary": "更新资源状态",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "id",
"in": "path",
"description": "",
"required": true,
"schema": {
"type": "integer"
}
},
{
"name": "status",
"in": "query",
"description": "",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
"schema": {
"type": "string",
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BaseResponseVoid"
}
}
}
}
},
"security": []
}
},
"/api/v1/resource/{id}/quantity": {
"put": {
"summary": "更新资源数量",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "id",
"in": "path",
"description": "",
"required": true,
"schema": {
"type": "integer"
}
},
{
"name": "actualQuantity",
"in": "query",
"description": "",
"required": true,
"schema": {
"type": "string"
}
},
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
"schema": {
"type": "string",
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BaseResponseVoid"
}
}
}
}
},
"security": []
}
},
"/api/v1/resource/stats/budget": {
"get": {
"summary": "查询资源预算汇总",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "projectId",
"in": "query",
"description": "",
"required": true,
"schema": {
"type": "integer"
}
},
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
"schema": {
"type": "string",
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BaseResponseListMapObject"
}
}
}
}
},
"security": []
}
},
"/api/v1/resource/pending-arrival": {
"get": {
"summary": "查询即将到位的资源",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "projectId",
"in": "query",
"description": "",
"required": true,
"schema": {
"type": "integer"
}
},
{
"name": "days",
"in": "query",
"description": "",
"required": true,
"example": 7,
"schema": {
"type": "integer"
}
},
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
"schema": {
"type": "string",
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BaseResponseListResource"
}
}
}
}
},
"security": []
}
},
"/api/v1/resource/pending-approval": {
"get": {
"summary": "查询待审批的资源申请",
"deprecated": false,
"description": "",
"tags": [],
"parameters": [
{
"name": "projectId",
"in": "query",
"description": "",
"required": false,
"schema": {
"type": "integer"
}
},
{
"name": "Authorization",
"in": "header",
"description": "",
"example": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95",
"schema": {
"type": "string",
"default": "Bearer b35c6f5b-bc0b-4652-bef2-eca04a5cdd95"
}
}
],
"responses": {
"200": {
"description": "",
"content": {
"application/json": {
"schema": {
"$ref": "#/components/schemas/BaseResponseListMapObject"
}
}
}
}
},
"security": []
}
}
},
"components": {
"schemas": {
"OrderItem": {
"type": "object",
"properties": {
"column": {
"type": "string",
"description": "需要进行排序的字段"
},
"asc": {
"type": "boolean",
"description": "是否正序排列,默认 true",
"default": true
}
}
},
"MapObject": {
"type": "object",
"properties": {
"key": {
"$ref": "#/components/schemas/key"
}
}
},
"PageMapObject": {
"type": "object",
"properties": {
"records": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MapObject"
},
"description": "查询数据列表",
"default": "Collections.emptyList()"
},
"total": {
"type": "integer",
"description": "总数",
"format": "int64",
"default": 0
},
"size": {
"type": "integer",
"description": "每页显示条数,默认 10",
"format": "int64",
"default": 10
},
"current": {
"type": "integer",
"description": "当前页",
"format": "int64",
"default": 1
},
"orders": {
"type": "array",
"items": {
"$ref": "#/components/schemas/OrderItem",
"description": "com.baomidou.mybatisplus.core.metadata.OrderItem"
},
"description": "排序字段信息",
"default": "new ArrayList<>()"
},
"optimizeCountSql": {
"type": "boolean",
"description": "自动优化 COUNT SQL",
"default": true
},
"searchCount": {
"type": "boolean",
"description": "是否进行 count 查询",
"default": true
},
"optimizeJoinOfCountSql": {
"type": "boolean",
"description": "{@link #optimizeJoinOfCountSql()}",
"default": true
},
"maxLimit": {
"type": "integer",
"description": "单页分页条数限制",
"format": "int64"
},
"countId": {
"type": "string",
"description": "countId"
},
"pages": {
"type": "integer",
"format": "int64"
}
}
},
"BaseResponsePageMapObject": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"description": ""
},
"data": {
"$ref": "#/components/schemas/PageMapObject",
"description": ""
},
"message": {
"type": "string",
"description": ""
}
}
},
"Resource": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "",
"format": "int64"
},
"resourceCode": {
"type": "string",
"description": "资源编号"
},
"projectId": {
"type": "integer",
"description": "项目ID",
"format": "int64"
},
"resourceType": {
"type": "string",
"description": "资源类型: human-人力, material-物料, equipment-设备, software-软件, finance-资金, other-其他"
},
"resourceName": {
"type": "string",
"description": "资源名称"
},
"description": {
"type": "string",
"description": "资源描述"
},
"specification": {
"type": "string",
"description": "规格型号"
},
"unit": {
"type": "string",
"description": "单位"
},
"planQuantity": {
"type": "number",
"description": "计划数量"
},
"actualQuantity": {
"type": "number",
"description": "实际数量"
},
"unitPrice": {
"type": "number",
"description": "单价"
},
"currency": {
"type": "string",
"description": "币种"
},
"supplier": {
"type": "string",
"description": "供应商/来源"
},
"status": {
"type": "string",
"description": "状态: planned-计划中, requested-已申请, approved-已批准, procuring-采购中, arrived-已到货, in_use-使用中, completed-已完成"
},
"planArriveDate": {
"type": "string",
"description": "计划到位日期"
},
"actualArriveDate": {
"type": "string",
"description": "实际到位日期"
},
"responsibleId": {
"type": "integer",
"description": "负责人ID",
"format": "int64"
},
"location": {
"type": "string",
"description": "存放位置"
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "标签"
},
"extraData": {
"type": "object",
"properties": {},
"description": "扩展数据"
},
"createBy": {
"type": "integer",
"description": "创建人",
"format": "int64"
},
"createTime": {
"type": "string",
"description": "创建时间"
},
"updateBy": {
"type": "integer",
"description": "更新人",
"format": "int64"
},
"updateTime": {
"type": "string",
"description": "更新时间"
},
"deleted": {
"type": "integer",
"description": "删除标记"
}
}
},
"key": {
"type": "object",
"properties": {}
},
"BaseResponseLong": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"description": ""
},
"data": {
"type": "integer",
"description": "",
"format": "int64"
},
"message": {
"type": "string",
"description": ""
}
}
},
"BaseResponseResource": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"description": ""
},
"data": {
"$ref": "#/components/schemas/Resource",
"description": ""
},
"message": {
"type": "string",
"description": ""
}
}
},
"BaseResponseVoid": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"description": ""
},
"data": {
"description": "",
"type": "null"
},
"message": {
"type": "string",
"description": ""
}
}
},
"ResourceUpdateRequest": {
"type": "object",
"properties": {
"id": {
"type": "integer",
"description": "资源ID必填",
"format": "int64"
},
"resourceType": {
"type": "string",
"description": "资源类型: human-人力, material-物料, equipment-设备, software-软件, finance-资金, other-其他"
},
"resourceName": {
"type": "string",
"description": "资源名称"
},
"description": {
"type": "string",
"description": "资源描述"
},
"specification": {
"type": "string",
"description": "规格型号"
},
"unit": {
"type": "string",
"description": "单位"
},
"planQuantity": {
"type": "number",
"description": "计划数量"
},
"actualQuantity": {
"type": "number",
"description": "实际数量"
},
"unitPrice": {
"type": "number",
"description": "单价"
},
"currency": {
"type": "string",
"description": "币种"
},
"supplier": {
"type": "string",
"description": "供应商/来源"
},
"status": {
"type": "string",
"description": "状态: planned-计划中, requested-已申请, approved-已批准, procuring-采购中, arrived-已到货, in_use-使用中, completed-已完成"
},
"planArriveDate": {
"type": "string",
"description": "计划到位日期"
},
"actualArriveDate": {
"type": "string",
"description": "实际到位日期"
},
"responsibleId": {
"type": "integer",
"description": "负责人ID直接传递ID时使用",
"format": "int64"
},
"responsibleName": {
"type": "string",
"description": "负责人姓名根据姓名自动匹配用户ID"
},
"location": {
"type": "string",
"description": "存放位置"
},
"tags": {
"type": "array",
"items": {
"type": "string"
},
"description": "标签"
},
"extraData": {
"type": "object",
"properties": {},
"description": "扩展数据"
}
}
},
"BaseResponseListMapObject": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"description": ""
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/MapObject"
},
"description": ""
},
"message": {
"type": "string",
"description": ""
}
}
},
"BaseResponseListResource": {
"type": "object",
"properties": {
"code": {
"type": "integer",
"description": ""
},
"data": {
"type": "array",
"items": {
"$ref": "#/components/schemas/Resource",
"description": "资源实体类\n对应数据库表: resource"
},
"description": ""
},
"message": {
"type": "string",
"description": ""
}
}
}
},
"responses": {},
"securitySchemes": {}
},
"servers": [],
"security": []
}

View File

@@ -10,12 +10,17 @@ import {
createMilestone,
updateMilestone,
deleteMilestone,
createResource,
updateResource,
deleteResource,
type ProjectDetail,
type ProjectMember,
type ProjectMilestone,
type ProjectTask,
type ProjectResource,
type ProjectRisk
type ProjectRisk,
type Resource,
type ResourceUpdateRequest
} from "@/api/project";
import { hasPerms } from "@/utils/auth";
import { GGanttChart, GGanttRow } from "@infectoone/vue-ganttastic";
@@ -56,6 +61,13 @@ const canCreateMilestone = computed(() => hasPerms("project:milestone:create"));
const canUpdateMilestone = computed(() => hasPerms("project:milestone:update"));
const canDeleteMilestone = computed(() => hasPerms("project:milestone:delete"));
// 权限控制 - 资源(基础权限)
const canCreateResource = computed(() => hasPerms("project:resource:create"));
const canUpdateResource = computed(() => hasPerms("project:resource:update"));
const canDeleteResource = computed(() => hasPerms("project:resource:delete"));
const canViewResource = computed(() => hasPerms("project:resource:view"));
const canStatsResource = computed(() => hasPerms("project:resource:stats"));
// 加载状态
const loading = ref(false);
const ganttLoading = ref(false);
@@ -121,6 +133,32 @@ const milestoneEditForm = ref<ProjectMilestone>({
const milestoneEditLoading = ref(false);
const isMilestoneEdit = ref(false); // true=编辑, false=新增
// 资源编辑模态框
const resourceEditModal = ref(false);
const resourceEditForm = ref<Resource>({
id: "",
resourceCode: "",
resourceType: "material",
resourceName: "",
description: "",
specification: "",
unit: "",
planQuantity: 0,
actualQuantity: 0,
unitPrice: 0,
currency: "CNY",
supplier: "",
status: "planned",
planArriveDate: "",
actualArriveDate: "",
responsibleId: "",
responsibleName: "",
location: "",
tags: []
});
const resourceEditLoading = ref(false);
const isResourceEdit = ref(false); // true=编辑, false=新增
// 权限控制 - 派生权限(需要放在 isTaskEdit/isMilestoneEdit 定义之后)
// 任务编辑权限:新增或编辑任一即可显示按钮
const canEditTask = computed(() => canCreateTask.value || canUpdateTask.value);
@@ -138,6 +176,16 @@ const canSaveMilestone = computed(() => {
? canUpdateMilestone.value
: canCreateMilestone.value;
});
// 资源编辑权限:新增或编辑任一即可显示按钮
const canEditResource = computed(
() => canCreateResource.value || canUpdateResource.value
);
// 资源保存权限新增时需要create权限编辑时需要update权限
const canSaveResource = computed(() => {
return isResourceEdit.value
? canUpdateResource.value
: canCreateResource.value;
});
// 项目基本信息(计算属性)
const projectInfo = computed(() => {
@@ -736,6 +784,103 @@ async function handleDeleteMilestone(milestoneId: string) {
}
}
// ==================== 资源编辑功能 ====================
/** 打开新增资源对话框 */
function openAddResourceModal() {
isResourceEdit.value = false;
resourceEditForm.value = {
id: "",
resourceCode: "",
resourceType: "material",
resourceName: "",
description: "",
specification: "",
unit: "",
planQuantity: 0,
actualQuantity: 0,
unitPrice: 0,
currency: "CNY",
supplier: "",
status: "planned",
planArriveDate: "",
actualArriveDate: "",
responsibleId: "",
responsibleName: "",
location: "",
tags: []
};
resourceEditModal.value = true;
}
/** 打开编辑资源对话框 */
function openEditResourceModal(resource: Resource) {
isResourceEdit.value = true;
resourceEditForm.value = { ...resource };
resourceEditModal.value = true;
}
/** 保存资源 */
async function saveResource() {
if (!resourceEditForm.value.resourceName) {
message("请输入资源名称", { type: "warning" });
return;
}
resourceEditLoading.value = true;
try {
if (isResourceEdit.value) {
const updateData: ResourceUpdateRequest = {
id: resourceEditForm.value.id!,
resourceType: resourceEditForm.value.resourceType,
resourceName: resourceEditForm.value.resourceName,
description: resourceEditForm.value.description,
specification: resourceEditForm.value.specification,
unit: resourceEditForm.value.unit,
planQuantity: resourceEditForm.value.planQuantity,
actualQuantity: resourceEditForm.value.actualQuantity,
unitPrice: resourceEditForm.value.unitPrice,
currency: resourceEditForm.value.currency,
supplier: resourceEditForm.value.supplier,
status: resourceEditForm.value.status,
planArriveDate: resourceEditForm.value.planArriveDate,
actualArriveDate: resourceEditForm.value.actualArriveDate,
responsibleId: resourceEditForm.value.responsibleId,
responsibleName: resourceEditForm.value.responsibleName,
location: resourceEditForm.value.location,
tags: resourceEditForm.value.tags
};
await updateResource(updateData);
message("资源更新成功", { type: "success" });
} else {
const createData = {
...resourceEditForm.value,
projectId: projectId.value
};
await createResource(createData);
message("资源创建成功", { type: "success" });
}
resourceEditModal.value = false;
await fetchProjectDetail();
} catch (error) {
console.error("保存资源失败:", error);
message("保存资源失败", { type: "error" });
} finally {
resourceEditLoading.value = false;
}
}
/** 删除资源 */
async function handleDeleteResource(resourceId: string) {
try {
await deleteResource(resourceId);
message("资源删除成功", { type: "success" });
await fetchProjectDetail();
} catch (error) {
console.error("删除资源失败:", error);
message("删除资源失败", { type: "error" });
}
}
onMounted(() => {
fetchProjectDetail();
fetchGanttData();
@@ -1210,11 +1355,27 @@ onMounted(() => {
<div class="flex items-center gap-2">
<component :is="useRenderIcon(FileListIcon)" />
<span class="font-medium">项目物料清单</span>
<el-tag size="small" type="info"
>{{ resourceList.length }} </el-tag
>
</div>
<div class="flex gap-2">
<el-button
v-if="canCreateResource"
type="primary"
size="small"
@click="openAddResourceModal"
>
<template #icon>
<component :is="useRenderIcon('ri/add-line')" />
</template>
新增资源
</el-button>
<el-button link type="primary">
查看全部
<component :is="useRenderIcon(ArrowRightIcon)" />
</el-button>
</div>
<el-button link type="primary">
查看全部
<component :is="useRenderIcon(ArrowRightIcon)" />
</el-button>
</div>
</template>
<el-table :data="resourceList" style="width: 100%">
@@ -1263,9 +1424,28 @@ onMounted(() => {
</div>
</template>
</el-table-column>
<el-table-column label="操作" width="80" fixed="right">
<template #default>
<el-button link type="primary" size="small">详情</el-button>
<el-table-column label="操作" width="130" fixed="right">
<template #default="{ row }">
<el-button
v-if="canUpdateResource"
link
type="primary"
size="small"
@click="openEditResourceModal(row)"
>
编辑
</el-button>
<el-popconfirm
v-if="canDeleteResource"
title="确定要删除该资源吗?"
@confirm="handleDeleteResource(row.id)"
>
<template #reference>
<el-button link type="danger" size="small">
删除
</el-button>
</template>
</el-popconfirm>
</template>
</el-table-column>
</el-table>
@@ -1603,6 +1783,183 @@ onMounted(() => {
</el-button>
</template>
</el-dialog>
<!-- 资源编辑模态框 -->
<el-dialog
v-model="resourceEditModal"
:title="isResourceEdit ? '编辑资源' : '新增资源'"
width="650px"
destroy-on-close
>
<el-form
ref="resourceFormRef"
:model="resourceEditForm"
label-width="100px"
class="resource-edit-form"
>
<el-row :gutter="16">
<el-col :span="24">
<el-form-item label="资源名称" prop="resourceName" required>
<el-input
v-model="resourceEditForm.resourceName"
placeholder="请输入资源名称"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="资源类型">
<el-select
v-model="resourceEditForm.resourceType"
placeholder="请选择资源类型"
style="width: 100%"
>
<el-option label="人力" value="human" />
<el-option label="物料" value="material" />
<el-option label="设备" value="equipment" />
<el-option label="软件" value="software" />
<el-option label="资金" value="finance" />
<el-option label="其他" value="other" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="规格型号">
<el-input
v-model="resourceEditForm.specification"
placeholder="请输入规格型号"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="计划数量">
<el-input-number
v-model="resourceEditForm.planQuantity"
:min="0"
:precision="2"
placeholder="数量"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单位">
<el-input
v-model="resourceEditForm.unit"
placeholder="如:个、台、套"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单价">
<el-input-number
v-model="resourceEditForm.unitPrice"
:min="0"
:precision="2"
placeholder="单价"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="币种">
<el-select
v-model="resourceEditForm.currency"
placeholder="请选择币种"
style="width: 100%"
>
<el-option label="人民币 (CNY)" value="CNY" />
<el-option label="美元 (USD)" value="USD" />
<el-option label="欧元 (EUR)" value="EUR" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="供应商">
<el-input
v-model="resourceEditForm.supplier"
placeholder="请输入供应商"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="状态">
<el-select
v-model="resourceEditForm.status"
placeholder="请选择状态"
style="width: 100%"
>
<el-option label="计划中" value="planned" />
<el-option label="已申请" value="requested" />
<el-option label="已批准" value="approved" />
<el-option label="采购中" value="procuring" />
<el-option label="已到货" value="arrived" />
<el-option label="使用中" value="in_use" />
<el-option label="已完成" value="completed" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="计划到位日期">
<el-date-picker
v-model="resourceEditForm.planArriveDate"
type="date"
placeholder="选择日期"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="实际到位日期">
<el-date-picker
v-model="resourceEditForm.actualArriveDate"
type="date"
placeholder="选择日期"
value-format="YYYY-MM-DD"
style="width: 100%"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="负责人">
<el-input
v-model="resourceEditForm.responsibleName"
placeholder="请输入负责人姓名"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="存放位置">
<el-input
v-model="resourceEditForm.location"
placeholder="请输入存放位置"
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="描述">
<el-input
v-model="resourceEditForm.description"
type="textarea"
:rows="3"
placeholder="请输入资源描述"
/>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="resourceEditModal = false">取消</el-button>
<el-button
v-if="canSaveResource"
type="primary"
:loading="resourceEditLoading"
@click="saveResource"
>
保存
</el-button>
</template>
</el-dialog>
</div>
</template>