Compare commits

...

91 Commits

Author SHA1 Message Date
Teo
32dc012598 合并 2025-09-09 10:07:21 +08:00
ee849ba62a 合并 2025-09-09 09:12:18 +08:00
Teo
0c3ea8fe73 提交 2025-09-09 09:08:34 +08:00
cd9e00fc33 优化 2025-09-09 09:05:28 +08:00
7738212dd7 优化 2025-09-08 20:08:32 +08:00
ljx
942375b06b 修改提交 2025-09-08 20:07:09 +08:00
cf2b805770 优化 2025-09-08 20:00:59 +08:00
Teo
4a9a0c4214 设计管理人员任命修复ui 2025-09-08 10:23:39 +08:00
Teo
e377368c41 合并 2025-09-08 10:22:41 +08:00
Teo
f341f08248 1 2025-09-08 10:21:17 +08:00
8eac9bce6c 合并 2025-09-08 09:49:11 +08:00
ljx
2ec6dcd74f 提交 2025-09-08 09:46:27 +08:00
6e33ae2972 优化 2025-09-08 09:41:59 +08:00
8631a697f4 合并 2025-09-06 19:20:55 +08:00
22a1379ca8 合并 2025-09-06 19:20:17 +08:00
38cf536f1b 合并 2025-09-06 19:18:50 +08:00
4f21a81706 优化 2025-09-06 19:18:00 +08:00
178f44a5a3 优化 2025-09-06 19:15:47 +08:00
dhr
4fa4c754f9 0906 2025-09-06 18:51:05 +08:00
tcy
5101dc7657 fix(ctr): 修复收入合同类型切换后未重置合同类型的问题
- 在切换收入合同类型时,确保合同类型被正确重置
- 优化了模板中的代码格式,提高了可读性
2025-09-06 18:26:17 +08:00
ljx
b412e847d4 提交 2025-09-06 18:24:12 +08:00
ljx
e39823db40 Merge branch 'main' of http://xny.yj-3d.com:3000/taoge/new_project into ljx 2025-09-06 18:24:00 +08:00
ljx
e71ec5b9c1 提交 2025-09-06 18:23:14 +08:00
ljx
52342e6623 修改9.06bug 2025-09-06 18:18:31 +08:00
ljx
dd594676ac 台账修改 2025-09-06 17:38:25 +08:00
6380ee505f 优化 2025-09-06 15:58:47 +08:00
Teo
c5dac4fbcc 合并 2025-09-06 15:27:22 +08:00
tcy
d50dbe719d refactor(formalities): 优化手续办理清单模板页面布局
- 调整了多个组件的样式和布局,包括表格、表单、按钮等
- 优化了部分代码结构,提高了代码可读性和维护性
- 添加了文件数量徽章,增强了用户界面的交互性
2025-09-06 15:19:02 +08:00
Teo
3fa3b5b508 1 2025-09-06 15:16:15 +08:00
tcy
dd12f54dc9 Merge branch 'main' of http://xny.yj-3d.com:3000/taoge/mk_system into tcy 2025-09-06 15:10:10 +08:00
tcy
d37243af6b refactor(volumeCatalog): 优化卷册目录页面布局和功能
- 调整表格列布局,增加文件数量徽章
- 优化导入按钮位置和样式
- 改进查看文件列表对话框,统一操作按钮样式
- 调整上传图纸和意见的组件样式
- 优化表格样式,增加对齐方式
2025-09-06 15:09:47 +08:00
3e4b85fc48 合并 2025-09-06 14:43:13 +08:00
b4dadc415d 优化 2025-09-06 14:41:47 +08:00
dhr
fff6fb5584 0905 2025-09-06 14:37:46 +08:00
dhr
3d50132338 0906 2025-09-06 13:21:10 +08:00
10538985ce 合并 2025-09-06 12:50:05 +08:00
2696d5cad5 合并 2025-09-06 12:46:03 +08:00
f0af5e47fa 优化 2025-09-06 12:45:15 +08:00
ljx
03ec2223aa 提交 2025-09-05 22:17:25 +08:00
c9496f4400 gps 2025-09-05 21:36:48 +08:00
shi
db20b45f78 style:修改用户弹窗样式 2025-09-05 17:28:00 +08:00
shi
78dd437746 Merge branch 'main' of http://192.168.110.2:3000/taoge/mk_system into szq 2025-09-05 16:03:39 +08:00
922af12760 优化 2025-09-05 16:01:49 +08:00
b910c6fee2 Merge branch 'dhr' of http://192.168.110.2:3000/taoge/mk_system into fs 2025-09-05 15:13:10 +08:00
dhr
eb895793dd 0905 2025-09-05 15:11:21 +08:00
19b48371ef 优化 2025-09-05 14:18:26 +08:00
26b2ff2370 优化 2025-09-05 10:53:30 +08:00
b96217b9bb 优化 2025-09-05 10:46:16 +08:00
ljx
f68e5f96f6 修改 2025-09-05 10:44:38 +08:00
dhr
c35a75d030 0904 2025-09-04 22:56:00 +08:00
974b2e7038 优化 2025-09-04 22:02:54 +08:00
f9a6ad7fda 优化 2025-09-04 18:55:48 +08:00
eaaf940960 合并 2025-09-04 18:55:11 +08:00
6f7d7e56c2 合并 2025-09-04 18:53:10 +08:00
0f439c9220 优化 2025-09-04 18:42:45 +08:00
Teo
2b989e1d46 采购设计报表新增tab切换 2025-09-04 18:40:25 +08:00
Teo
26bef3f724 修复bug 2025-09-04 17:56:01 +08:00
shi
e075ce1bfc style 2025-09-04 17:36:13 +08:00
a257068054 打卡范围 2025-09-04 17:11:18 +08:00
Teo
80bb267e7d 合并 2025-09-04 11:11:22 +08:00
Teo
57460d0af1 增加搜索 2025-09-04 11:10:55 +08:00
4dd1f2d6d5 1 2025-09-04 10:56:28 +08:00
17ee714729 11 2025-09-04 10:37:24 +08:00
08556fbf7f 111 2025-09-04 10:36:43 +08:00
23b6551829 合并 2025-09-04 09:04:27 +08:00
9b06c042ef 优化 2025-09-04 09:03:42 +08:00
ljx
ab7d51d3a2 Merge branch 'main' of http://xny.yj-3d.com:3000/taoge/new_project into ljx 2025-09-03 17:31:25 +08:00
ljx
5f6547aeb1 提交 2025-09-03 17:31:16 +08:00
d26db2d321 优化 2025-09-03 17:28:50 +08:00
9d9224d6ad 合并 2025-09-03 15:44:29 +08:00
055811702b 优化 2025-09-03 15:42:10 +08:00
ljx
eb98e8c8c3 修改禅道bug 2025-09-03 15:10:20 +08:00
ljx
288ee14d5e Merge branch 'main' of http://xny.yj-3d.com:3000/taoge/new_project into ljx 2025-09-03 11:56:52 +08:00
ljx
26e4c899db 修改bug 2025-09-02 20:37:07 +08:00
a95d18906f 优化 2025-09-02 19:34:26 +08:00
2737e579c8 合并 2025-09-02 19:20:07 +08:00
069841ea99 合并 2025-09-02 19:16:42 +08:00
ljx
85e5572e53 修改bug 2025-09-02 19:15:36 +08:00
46f9c0568c 合并 2025-09-02 19:14:34 +08:00
1a476b8ee8 优化 2025-09-02 19:13:43 +08:00
ljx
d06d6a9504 修改禅道bug 2025-09-02 19:09:43 +08:00
ljx
0c81842fd6 Merge branch 'main' of http://xny.yj-3d.com:3000/taoge/new_project into ljx 2025-09-02 19:03:49 +08:00
ljx
2ba08c79ed 修改禅道bug 2025-09-02 19:00:50 +08:00
ddccc64586 工程量清单 2025-09-02 15:08:39 +08:00
0c0e55a383 Merge branch 'main' of http://192.168.110.2:3000/taoge/mk_system into fs 2025-09-02 14:59:06 +08:00
253e395ace 优化 2025-09-02 09:52:36 +08:00
192ffb5091 合并 2025-09-01 09:22:33 +08:00
9cecbeaa20 合并 2025-09-01 09:22:12 +08:00
shi
290fc16c32 略改样式 2025-08-25 19:59:36 +08:00
shi
e4523299d4 完善项目及大屏 2025-08-22 22:17:57 +08:00
shi
a712dce2cd 添加屏幕扩展动画 2025-08-22 17:47:58 +08:00
116 changed files with 11164 additions and 3383 deletions

View File

@ -0,0 +1 @@

Binary file not shown.

View File

@ -24,3 +24,11 @@ export const exportWord = (params) => {
method: 'post'
});
};
// 导出模版
export const exportExcel = (params) => {
return request({
url: '/design/collect/exportExcel',
method: 'post',
params: params
});
};

View File

@ -127,6 +127,17 @@ export const majorList = (params) => {
params: params
});
};
/**
* 获取人员列表
* @param query
*/
export const copyUserList = (params) => {
return request({
url: '/design/volumeCatalog/copyUserList',
method: 'get',
params: params
});
};
/**
* 获取二维码信息
* @param query

111
src/api/equipment/index.ts Normal file
View File

@ -0,0 +1,111 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { EquipmentVO, EquipmentForm, EquipmentQuery } from '@/api/equipment/types';
/**
* 查询GPS设备详细列表
* @param query
* @returns {*}
*/
export const listEquipment = (query?: EquipmentQuery): AxiosPromise<EquipmentVO[]> => {
return request({
url: '/gps/equipment/list',
method: 'get',
params: query
});
};
/**
* 查询GPS设备详细详细
* @param id
*/
export const getEquipment = (id: string | number): AxiosPromise<EquipmentVO> => {
return request({
url: '/gps/equipment/' + id,
method: 'get'
});
};
/**
* 新增GPS设备详细
* @param data
*/
export const addEquipment = (data: EquipmentForm) => {
return request({
url: '/gps/equipment',
method: 'post',
data: data
});
};
/**
* 修改GPS设备详细
* @param data
*/
export const updateEquipment = (data: EquipmentForm) => {
return request({
url: '/gps/equipment',
method: 'put',
data: data
});
};
/**
* 删除GPS设备详细
* @param id
*/
export const delEquipment = (id: string | number | Array<string | number>) => {
return request({
url: '/gps/equipment/' + id,
method: 'delete'
});
};
export const bindUser = (data) => {
return request({
url: '/gps/equipment/bindManmachine',
method: 'post',
data: data
});
};
export const getUserId = (projectId) => {
return request({
url: '/gps/equipment/userList',
method: 'get',
params: { projectId }
});
};
export const gethistroyUser = (data) => {
return request({
url: '/gps/equipment/getUserList',
method: 'get',
params: data
});
};
// 解除绑定接口定义确保使用POST方法并正确传递data
export function getRemoveBind(data: { id: number; clientId: string }) {
return request({
url: '/gps/equipment/unbindManmachine',
method: 'post',
data: data
});
}
export function getProjectId() {
return request({
url: 'gps/equipment/getProjectList',
method: 'get'
});
}
export const getFootNote = (data) => {
return request({
url: 'gps/equipmentSon/getList',
method: 'get',
data: data
});
};

169
src/api/equipment/types.ts Normal file
View File

@ -0,0 +1,169 @@
export interface EquipmentVO {
/**
*
*/
id: string | number;
/**
* 项目ID
*/
projectId: string | number;
/**
* 用户id
*/
userId: string | number;
/**
* 设备标识
*/
clientId: string | number;
/**
* 设备名称
*/
deviceName: string;
/**
* 是否使用UDP协议0=否1=是
*/
udp: number;
/**
* 远程连接地址IP:端口)
*/
remoteAddressStr: string;
/**
* 连接创建时间
*/
creationTime: number;
/**
* 最后活动时间
*/
lastAccessedTime: number;
/**
* 是否已注册0=未注册1=已注册
*/
registered: number;
/**
* 备注
*/
remark: string;
}
export interface EquipmentForm extends BaseEntity {
/**
*
*/
id?: string | number;
/**
* 项目ID
*/
projectId?: string | number;
/**
* 用户id
*/
userId?: string | number;
/**
* 设备标识
*/
clientId?: string | number;
/**
* 设备名称
*/
deviceName?: string;
/**
* 是否使用UDP协议0=否1=是
*/
udp?: number;
/**
* 远程连接地址IP:端口)
*/
remoteAddressStr?: string;
/**
* 连接创建时间
*/
creationTime?: number;
/**
* 最后活动时间
*/
lastAccessedTime?: number;
/**
* 是否已注册0=未注册1=已注册
*/
registered?: number;
/**
* 备注
*/
remark?: string;
}
export interface EquipmentQuery extends PageQuery {
/**
* 项目ID
*/
projectId?: string | number;
/**
* 是否绑定
*/
type?: string | number;
/**
* 用户id
*/
userId?: string | number;
/**
* 设备标识
*/
clientId?: string | number;
/**
* 设备名称
*/
deviceName?: string;
/**
* 是否使用UDP协议0=否1=是
*/
udp?: number;
/**
* 远程连接地址IP:端口)
*/
remoteAddressStr?: string;
/**
* 连接创建时间
*/
creationTime?: number;
/**
* 最后活动时间
*/
lastAccessedTime?: number;
/**
* 是否已注册0=未注册1=已注册
*/
registered?: number;
/**
* 日期范围参数
*/
params?: any;
}

View File

@ -14,6 +14,18 @@ export const listCompany = (query?: CompanyQuery): AxiosPromise<CompanyVO[]> =>
method: 'get',
params: query
});
}; /**
* 查询材料提供商
* @param query
* @returns {*}
*/
export const supplierInputGet = (query?) => {
return request({
url: '/supplierInput/supplierInput/getList',
method: 'get',
params: query
});
};
/**

View File

@ -75,3 +75,11 @@ export const inventoryList = (id: any) => {
method: 'get'
});
};
//获取材料表信息
export const getMaterialInfo = (id: any) => {
return request({
url: '/materials/materials/listByFormCode/' + id,
method: 'get'
});
};

View File

@ -0,0 +1,27 @@
import request from '@/utils/request';
//获取出库材料得列表
export const outboundMaterials = (query?: any) => {
return request({
url: '/materials/materials/listRelevancy',
method: 'get',
params: query
});
};
//新增出库
export const addOutbound = (data?: any) => {
return request({
url: '/materials/materialsInventory',
method: 'post',
data
});
};
//获取材料列表
export const getMaterialsList = (query?: any) => {
return request({
url: '/materials/materials/list',
method: 'get',
params: query
});
};

View File

@ -61,3 +61,13 @@ export const delMaterialsInventory = (id: string | number | Array<string | numbe
method: 'delete'
});
};
//获取新的列表数据
export const getLedgerList = (query?: any) => {
return request({
url: '/materials/materials/listUseDetail',
method: 'get',
params: query
});
};
//导出

View File

@ -33,7 +33,6 @@ export interface MaterialsUseRecordVO {
* 备注
*/
remark: string;
}
export interface MaterialsUseRecordForm extends BaseEntity {
@ -71,11 +70,9 @@ export interface MaterialsUseRecordForm extends BaseEntity {
* 备注
*/
remark?: string;
}
export interface MaterialsUseRecordQuery extends PageQuery {
/**
* 项目ID
*/
@ -101,11 +98,9 @@ export interface MaterialsUseRecordQuery extends PageQuery {
*/
residueNumber?: string | number;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
materialsId?: string | number;
}

View File

@ -8,7 +8,7 @@ import { MonthPlanVO, MonthPlanForm, MonthPlanQuery } from '@/api/out/monthPlan/
* @returns {*}
*/
export const listMonthPlan = (query?: MonthPlanQuery): AxiosPromise<MonthPlanVO[]> => {
export const listMonthPlan = (query?: any) => {
return request({
url: '/out/monthPlan/list',
method: 'get',
@ -93,7 +93,7 @@ export const isSubmit = (id): AxiosPromise => {
*/
export const getMonthInfo = (query): AxiosPromise<MonthPlanVO> => {
return request({
url: '/out/monthPlan/monthInfo',
url: '/out/monthPlan/monthInfo/' + query.id,
method: 'get',
params: query
});
@ -120,4 +120,4 @@ export const purchaseValueA = (query) => {
method: 'get',
params: query
});
};
};

View File

@ -8,10 +8,11 @@ import { ProgressCategoryVO, ProgressCategoryForm, ProgressCategoryQuery } from
* @returns {*}
*/
export const listProgressCategory = (id?: string | number): AxiosPromise<any[]> => {
export const listProgressCategory = (parentId?: string | number,name?:string): AxiosPromise<any[]> => {
return request({
url: '/progress/progressCategory/listByParent/' + id,
method: 'get'
url: '/progress/progressCategory/listByParent',
method: 'get',
params:{parentId,name}
});
};

View File

@ -78,9 +78,10 @@ export const getTabList = (id: string) => {
* @param parentId
* @returns {*}
*/
export const listProgressCategoryTemplateByParent = (parentId: string | number): AxiosPromise<ProgressCategoryTemplateVO[]> => {
export const listProgressCategoryTemplateByParent = (parentId: string | number,name:string): AxiosPromise<ProgressCategoryTemplateVO[]> => {
return request({
url: '/progress/progressCategoryTemplate/listByParent/' + parentId,
method: 'get'
url: '/progress/progressCategoryTemplate/listByParent' ,
method: 'get',
params:{parentId,name}
});
};

View File

@ -226,3 +226,30 @@ export const importConstructionUserInfo = (file: string) => {
data: { file }
});
};
// 获取项目列表
export const ProjectList = (query) => {
return request({
url: '/contractor/constructionUser/projectList',
method: 'get',
params: query
});
};
// 获取班组列表
export const TeamList = (query) => {
return request({
url: '/contractor/constructionUser/teamList',
method: 'get',
params: query
});
};
// 班组分配
export const TeamDistribution = (data) => {
return request({
url: '/contractor/constructionUser/addTeam',
method: 'post',
data: data
});
};

View File

@ -182,6 +182,8 @@ export interface ConstructionUserVO {
* 创建时间
*/
createTime: string;
sysUserId: string | number;
}
export interface skipType {
/**

View File

@ -23,7 +23,7 @@ export const listConstructionUserFile = (query?: ConstructionUserFileQuery): Axi
*/
export const setConstructionUserFile = (data: ConstructionUserFileForm): AxiosPromise<string | number> => {
return request({
url: '/project/constructionUserFile/save',
url: '/contractor/constructionUserFile/save',
method: 'post',
data
});
@ -35,7 +35,7 @@ export const setConstructionUserFile = (data: ConstructionUserFileForm): AxiosPr
*/
export const delConstructionUserFile = (id: string | number | Array<string | number>) => {
return request({
url: '/project/constructionUserFile/' + id,
url: '/contractor/constructionUserFile/' + id,
method: 'delete'
});
};

View File

@ -196,3 +196,71 @@ export const changeProject = (id: string | number) => {
method: 'get'
});
};
/**
* 打卡规则
* @param id
*/
export const attendanceRuleEdit = (data) => {
return request({
url: '/project/attendanceRule',
method: 'put',
data
});
};
/**
* 打卡规则
* @param id
*/
export const attendanceRuleAdd = (data) => {
return request({
url: '/project/attendanceRule',
method: 'post',
data
});
};
/**
* 获取规则
* @param id
*/
export const byProjectIdDetail = (id) => {
return request({
url: '/project/attendanceRule/byProjectId/' + id,
method: 'get'
});
};
// 新增项目打卡范围
export const addAttendanceRange = (data) => {
return request({
url: '/project/projectPunchrange',
method: 'post',
data
});
};
// 删除项目打卡范围
export const delAttendanceRange = (id) => {
return request({
url: '/project/projectPunchrange/' + id,
method: 'delete'
});
};
// 修改项目打卡范围
export const updateAttendanceRange = (data) => {
return request({
url: '/project/projectPunchrange',
method: 'put',
data
});
};
// 查询项目打卡范围列表
export const getAttendanceRangeList = (data) => {
return request({
url: '/project/projectPunchrange/list',
method: 'get',
params: data
});
};

View File

@ -55,6 +55,11 @@ export interface ProjectTeamForm extends BaseEntity {
* 备注
*/
remark?: string;
/**
* 创建时间
*/
punchRangeList?: [];
}
export interface ProjectTeamQuery extends PageQuery {

View File

@ -0,0 +1,66 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
// 查询生项目天气
export const getScreenWeather = (projectId: number | string) => {
return request({
url: '/project/big/screen/weather/' + projectId,
method: 'get',
});
};
// 查询项目安全天数
export const getScreenSafetyDay = (projectId: number | string) => {
return request({
url: '/project/big/screen/safetyDay/' + projectId,
method: 'get',
});
};
// 查询项目公告
export const getScreenNews = (projectId: number | string) => {
return request({
url: '/project/big/screen/news/' + projectId,
method: 'get',
});
};
// 查询项目土地统计
export const getScreenLand = (projectId: number | string) => {
return request({
url: '/project/big/screen/' + projectId,
method: 'get',
});
};
// 查询项目形象进度
export const getScreenImgProcess = (projectId: number | string) => {
return request({
url: '/project/big/screen/imageProgress/' + projectId,
method: 'get',
});
};
// 查询项目人员情况
export const getScreenPeople = (projectId: number | string) => {
return request({
url: '/project/big/screen/people/' + projectId,
method: 'get',
});
};
// 查询项目AI安全巡检
export const getScreenSafetyInspection = (projectId: number | string) => {
return request({
url: '/project/big/screen/safetyInspection/' + projectId,
method: 'get',
});
};
// 查询项目概况
export const getScreenGeneralize = (projectId: number | string) => {
return request({
url: '/project/big/screen/generalize/' + projectId,
method: 'get',
});
};

View File

@ -0,0 +1,5 @@
export interface TableQuery extends PageQuery {
tableName: string;
tableComment: string;
dataName: string;
}

View File

@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { QuestionBankVO, QuestionBankForm, QuestionBankQuery } from '@/api/safety/questionBank/types';
import { QuestionBankVO, QuestionBankForm, QuestionBankQuery } from '@/api/safety/wgzQuestionBank/types';
/**
* 查询题库列表
@ -10,7 +10,7 @@ import { QuestionBankVO, QuestionBankForm, QuestionBankQuery } from '@/api/safet
export const listQuestionBank = (query?: QuestionBankQuery): AxiosPromise<QuestionBankVO[]> => {
return request({
url: '/safety/questionBank/list',
url: '/safety/wgzQuestionBank/list',
method: 'get',
params: query
});
@ -22,7 +22,7 @@ export const listQuestionBank = (query?: QuestionBankQuery): AxiosPromise<Questi
*/
export const getQuestionBank = (id: string | number): AxiosPromise<QuestionBankVO> => {
return request({
url: '/safety/questionBank/' + id,
url: '/safety/wgzQuestionBank/' + id,
method: 'get'
});
};
@ -33,7 +33,7 @@ export const getQuestionBank = (id: string | number): AxiosPromise<QuestionBankV
*/
export const addQuestionBank = (data: QuestionBankForm) => {
return request({
url: '/safety/questionBank',
url: '/safety/wgzQuestionBank',
method: 'post',
data: data
});
@ -45,7 +45,7 @@ export const addQuestionBank = (data: QuestionBankForm) => {
*/
export const updateQuestionBank = (data: QuestionBankForm) => {
return request({
url: '/safety/questionBank',
url: '/safety/wgzQuestionBank',
method: 'put',
data: data
});
@ -57,7 +57,7 @@ export const updateQuestionBank = (data: QuestionBankForm) => {
*/
export const delQuestionBank = (id: string | number | Array<string | number>) => {
return request({
url: '/safety/questionBank/' + id,
url: '/safety/wgzQuestionBank/' + id,
method: 'delete'
});
};

View File

@ -1,6 +1,6 @@
import request from '@/utils/request';
import { AxiosPromise } from 'axios';
import { QuestionUserAnswerVO, QuestionUserAnswerForm, QuestionUserAnswerQuery } from '@/api/safety/questionUserAnswer/types';
import { QuestionUserAnswerVO, QuestionUserAnswerForm, QuestionUserAnswerQuery } from '@/api/safety/wgzQuestionSave/types';
/**
* 查询用户试卷存储列表
@ -10,7 +10,7 @@ import { QuestionUserAnswerVO, QuestionUserAnswerForm, QuestionUserAnswerQuery }
export const listQuestionUserAnswer = (query?: QuestionUserAnswerQuery): AxiosPromise<QuestionUserAnswerVO[]> => {
return request({
url: '/safety/questionUserAnswer/list',
url: '/safety/wgzQuestionSave/list',
method: 'get',
params: query
});
@ -22,7 +22,7 @@ export const listQuestionUserAnswer = (query?: QuestionUserAnswerQuery): AxiosPr
*/
export const getQuestionUserAnswer = (id: string | number): AxiosPromise<QuestionUserAnswerVO> => {
return request({
url: '/safety/questionUserAnswer/' + id,
url: '/safety/wgzQuestionSave/' + id,
method: 'get'
});
};
@ -33,7 +33,7 @@ export const getQuestionUserAnswer = (id: string | number): AxiosPromise<Questio
*/
export const addQuestionUserAnswer = (data: QuestionUserAnswerForm) => {
return request({
url: '/safety/questionUserAnswer',
url: '/safety/wgzQuestionSave',
method: 'post',
data: data
});
@ -45,7 +45,7 @@ export const addQuestionUserAnswer = (data: QuestionUserAnswerForm) => {
*/
export const updateQuestionUserAnswer = (data: QuestionUserAnswerForm) => {
return request({
url: '/safety/questionUserAnswer',
url: '/safety/wgzQuestionSave',
method: 'put',
data: data
});
@ -57,7 +57,7 @@ export const updateQuestionUserAnswer = (data: QuestionUserAnswerForm) => {
*/
export const delQuestionUserAnswer = (id: string | number | Array<string | number>) => {
return request({
url: '/safety/questionUserAnswer/' + id,
url: '/safety/wgzQuestionSave/' + id,
method: 'delete'
});
};
@ -68,7 +68,7 @@ export const delQuestionUserAnswer = (id: string | number | Array<string | numbe
*/
export const uploadQuestionUserAnswer = (data: any) => {
return request({
url: '/safety/questionUserAnswer/upload/zip',
url: '/safety/wgzQuestionSave/upload/zip',
method: 'post',
data: data
});

View File

@ -10,7 +10,7 @@ import { QuestionsCategoryVO, QuestionsCategoryForm, QuestionsCategoryQuery } fr
export const listQuestionsCategory = (query?: QuestionsCategoryQuery): AxiosPromise<QuestionsCategoryVO[]> => {
return request({
url: '/safety/questionsCategory/list',
url: '/safety/wzgQuestionCategory/list',
method: 'get',
params: query
});
@ -22,7 +22,7 @@ export const listQuestionsCategory = (query?: QuestionsCategoryQuery): AxiosProm
*/
export const getQuestionsCategory = (id: string | number): AxiosPromise<QuestionsCategoryVO> => {
return request({
url: '/safety/questionsCategory/' + id,
url: '/safety/wzgQuestionCategory' + id,
method: 'get'
});
};
@ -33,7 +33,7 @@ export const getQuestionsCategory = (id: string | number): AxiosPromise<Question
*/
export const addQuestionsCategory = (data: QuestionsCategoryForm) => {
return request({
url: '/safety/questionsCategory',
url: '/safety/wzgQuestionCategory',
method: 'post',
data: data
});
@ -45,7 +45,7 @@ export const addQuestionsCategory = (data: QuestionsCategoryForm) => {
*/
export const updateQuestionsCategory = (data: QuestionsCategoryForm) => {
return request({
url: '/safety/questionsCategory',
url: '/safety/wzgQuestionCategory',
method: 'put',
data: data
});
@ -57,7 +57,7 @@ export const updateQuestionsCategory = (data: QuestionsCategoryForm) => {
*/
export const delQuestionsCategory = (id: string | number | Array<string | number>) => {
return request({
url: '/safety/questionsCategory/' + id,
url: '/safety/wzgQuestionCategory' + id,
method: 'delete'
});
};

View File

@ -22,7 +22,7 @@ export const listQuestionsConfig = (query?: QuestionsConfigQuery): AxiosPromise<
*/
export const getQuestionsConfig = (id: string | number): AxiosPromise<QuestionsConfigVO> => {
return request({
url: '/safety/questionsConfig/' + id,
url: '/safety/wzgQuestionsConfiguration/' + id,
method: 'get'
});
};
@ -45,7 +45,7 @@ export const addQuestionsConfig = (data: QuestionsConfigForm) => {
*/
export const updateQuestionsConfig = (data: QuestionsConfigForm) => {
return request({
url: '/safety/questionsConfig',
url: '/safety/wzgQuestionsConfiguration',
method: 'put',
data: data
});

View File

@ -79,3 +79,26 @@ export const landTransferLedgerCount = (id: string | number | Array<string | num
method: 'get'
});
};
export const addSonLandTransferLedger = (data) => {
return request({
url: '/land/landTransferLedger/children/add',
method: 'post',
data: data
});
};
export const listSonLandTransferLedger = (query) => {
return request({
url: '/land/landTransferLedger/children/list',
method: 'get',
params: query
});
};
// api/yourApiModule.js
export const listallCountValue = (projectId) => {
return request({
url: '/land/landTransferLedger/allCountValue/' + projectId,
method: 'get'
});
};

View File

@ -4,6 +4,11 @@ export interface LandTransferLedgerVO {
*/
id: string | number;
/**
* 父级ID
*/
parentId: string | number;
/**
* 项目ID
*/
@ -83,7 +88,6 @@ export interface LandTransferLedgerVO {
* 下一步策略
*/
nextStrategy: string;
}
export interface LandTransferLedgerForm extends BaseEntity {
@ -92,6 +96,11 @@ export interface LandTransferLedgerForm extends BaseEntity {
*/
id?: string | number;
/**
* 父级ID
*/
parentId: string | number;
/**
* 项目ID
*/
@ -171,11 +180,9 @@ export interface LandTransferLedgerForm extends BaseEntity {
* 下一步策略
*/
nextStrategy?: string;
}
export interface LandTransferLedgerQuery extends PageQuery {
/**
* 项目ID
*/
@ -256,11 +263,8 @@ export interface LandTransferLedgerQuery extends PageQuery {
*/
nextStrategy?: string;
/**
* 日期范围参数
*/
params?: any;
/**
* 日期范围参数
*/
params?: any;
}

View File

@ -20,18 +20,20 @@ export const getMenu = (menuId: string | number): AxiosPromise<MenuVO> => {
};
// 查询菜单下拉树结构
export const treeselect = (): AxiosPromise<MenuTreeOption[]> => {
export const treeselect = (params?: any): AxiosPromise<MenuTreeOption[]> => {
return request({
url: '/system/menu/treeselect',
method: 'get'
method: 'get',
params
});
};
// 根据角色ID查询菜单下拉树结构
export const roleMenuTreeselect = (roleId: string | number): AxiosPromise<RoleMenuTree> => {
export const roleMenuTreeselect = (roleId: string | number, params?: any): AxiosPromise<RoleMenuTree> => {
return request({
url: '/system/menu/roleMenuTreeselect/' + roleId,
method: 'get'
method: 'get',
params
});
};

View File

@ -147,10 +147,11 @@ export const authUserSelectAll = (data: any) => {
});
};
// 根据角色ID查询部门树结构
export const deptTreeSelect = (roleId: string | number): AxiosPromise<RoleDeptTree> => {
export const deptTreeSelect = (roleId: string | number, params?) => {
return request({
url: '/system/role/deptTree/' + roleId,
method: 'get'
method: 'get',
params
});
};

Binary file not shown.

After

Width:  |  Height:  |  Size: 248 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 652 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 665 B

Binary file not shown.

Before

Width:  |  Height:  |  Size: 104 KiB

View File

@ -208,6 +208,7 @@ watch(
});
} else {
fileList.value = [];
return [];
}
},
@ -299,7 +300,7 @@ const handleChange = (file: any, filelist: any) => {
}
}
// 记录 status = 'ready' 的文件
if (file.status === 'ready') {
if (file.status === 'ready' && !props.isConstruction) {
pendingFiles.value.push(file);
fileList.value = pendingFiles.value;
}

View File

@ -201,6 +201,8 @@ const uploadedSuccessfully = () => {
fileList.value = fileList.value.filter((f) => f.url !== undefined).concat(uploadList.value);
uploadList.value = [];
number.value = 0;
console.log(fileList.value);
emit('update:modelValue', listToString(fileList.value));
proxy?.$modal.closeLoading();
}

View File

@ -170,8 +170,6 @@ onMounted(() => {
//分页
const getWaitingList = () => {
pageByTaskWait({ pageNum: 1, pageSize: 10 }).then((resp) => {
console.log(resp);
total.value = resp.total;
});
};

View File

@ -7,6 +7,7 @@
<template #title>
<span class="menu-title" :title="hasTitle(onlyOneChild.meta.title)">{{ onlyOneChild.meta.title }}</span>
<span class="bage" v-if="onlyOneChild.meta?.title == '我的待办' && total > 0">{{ total }}</span>
<span class="bage" v-if="onlyOneChild.meta?.title == '我的抄送' && totalChao > 0">{{ totalChao }}</span>
</template>
</el-menu-item>
</app-link>
@ -16,7 +17,7 @@
<template v-if="item.meta" #title>
<svg-icon :icon-class="item.meta ? item.meta.icon : ''" />
<span class="menu-title" :title="hasTitle(item.meta?.title)">{{ item.meta?.title }}</span>
<!-- <span class="bage" v-if="item.meta?.title == '我的任务' && total >= 0">{{ total }}</span> -->
<span class="bage" v-if="item.meta?.title == '我的任务' && total > 0">{{ total }}</span>
</template>
<sidebar-item
@ -36,7 +37,7 @@ import { isExternal } from '@/utils/validate';
import AppLink from './Link.vue';
import { getNormalPath } from '@/utils/ruoyi';
import { RouteRecordRaw } from 'vue-router';
import { pageByTaskWait } from '@/api/workflow/task';
import { pageByTaskWait, pageByTaskCopy } from '@/api/workflow/task';
import useUserStore from '@/store/modules/user';
import useNoticeStore from '@/store/modules/notice';
const userStore = useUserStore();
@ -56,21 +57,27 @@ const props = defineProps({
}
});
const total = ref(0);
const totalChao = ref(0);
onMounted(() => {
if (onlyOneChild.value.meta?.title == '我的待办' || props.item.meta?.title == '我的任务') {
console.log(44444444);
getWaitingList();
}
if (onlyOneChild.value.meta?.title == '我的抄送') {
getChaoList();
}
});
// 获取我的待办
//分页
const getWaitingList = () => {
pageByTaskWait({ pageNum: 1, pageSize: 10 }).then((resp) => {
console.log(resp);
total.value = resp.total;
});
};
// 获取我的抄送
const getChaoList = () => {
pageByTaskCopy({ pageNum: 1, pageSize: 10 }).then((resp) => {
totalChao.value = resp.total;
});
};
const onlyOneChild = ref<any>({});
const hasOneShowingChild = (parent: RouteRecordRaw, children?: RouteRecordRaw[]) => {
@ -123,9 +130,7 @@ const hasTitle = (title: string | undefined): string => {
watch(
() => noticeStore.state.value.notices,
(newVal) => {
if (onlyOneChild.value.meta?.title == '我的待办') {
console.log(121212121);
if (onlyOneChild.value.meta?.title == '我的待办' || props.item.meta?.title == '我的任务') {
let time = setTimeout(() => {
getWaitingList();
clearTimeout(time);

View File

@ -5,8 +5,6 @@ import { ProjectTeamVO } from '@/api/project/projectTeam/types';
import useUserStore from '@/store/modules/user';
export const getProjectTeam = async () => {
const isPermission = useUserStore().permissions.some((item) => item == 'project:team:list');
console.log(useUserStore().permissions);
if (!isPermission && useUserStore().permissions[0] != '*:*:*') return;
const { goId } = $cache.local.getJSON('selectedProject');

View File

@ -1,26 +1,27 @@
<template>
<div class="centerPage">
<div class="topPage">
<!-- 暂无 -->
<div id="earth" style="width: 100%;height: 100%;"></div>
</div>
<div class="endPage">
<Title title="AI安全巡检" :prefix="true" />
<div class="swiper">
<div class="endPage" :class="{ 'slide-out-down': isHide }">
<Title title="AI安全巡检">
<img src="@/assets/projectLarge/robot.svg" alt="" height="20px" width="20px">
</Title>
<div class="swiper" v-if="inspectionList.length">
<div class="arrow" :class="{ 'canUse': canLeft }" @click="swiperClick('left')">
<el-icon size="16" color="skyblue">
<el-icon size="16" :color="canLeft ? 'rgba(29, 214, 255, 1)' : 'rgba(29, 214, 255, 0.3)'">
<ArrowLeft />
</el-icon>
</div>
<div class="swiper_content" ref="swiperContent">
<div class="swiper_item" v-for="(item, index) in swiperList" :key="index">
<img src="@/assets/projectLarge/swiper.png" alt="" class="swiper_img">
<div class="swiper_date">{{ item.date }}</div>
<div class="swiper_tip">{{ item.tip }}</div>
<div class="swiper_item" v-for="(item, i) in inspectionList" :key="i">
<img :src="item.picture" alt="安全巡检" class="swiper_img">
<div class="swiper_date">{{ item.createTime.slice(5) }}</div>
<div class="swiper_tip">{{ item.label }}</div>
</div>
</div>
<div class="arrow" :class="{ 'canUse': canRight }" @click="swiperClick('right')">
<el-icon size="16">
<el-icon size="16" :color="canRight ? 'rgba(29, 214, 255, 1)' : 'rgba(29, 214, 255, 0.3)'">
<ArrowRight />
</el-icon>
</div>
@ -29,31 +30,39 @@
</div>
</template>
<script setup lang="ts">
import { ref } from "vue"
<script setup>
import { ref, onMounted, toRefs, getCurrentInstance } from "vue"
import Title from './title.vue'
import { ArrowLeft, ArrowRight } from '@element-plus/icons-vue'
import { getScreenSafetyInspection } from '@/api/projectScreen'
const swiperList = ref([
{ date: '03-18 15:00', tip: '未佩戴安全帽1' },
{ date: '03-18 15:00', tip: '未佩戴安全帽2' },
{ date: '03-18 15:00', tip: '未佩戴安全帽3' },
{ date: '03-18 15:00', tip: '未佩戴安全帽4' },
{ date: '03-18 15:00', tip: '未佩戴安全帽5' },
{ date: '03-18 15:00', tip: '未佩戴安全帽6' },
{ date: '03-18 15:00', tip: '未佩戴安全帽7' },
{ date: '03-18 15:00', tip: '未佩戴安全帽8' },
{ date: '03-18 15:00', tip: '未佩戴安全帽9' },
{ date: '03-18 15:00', tip: '未佩戴安全帽10' },
{ date: '03-18 15:00', tip: '未佩戴安全帽11' },
{ date: '03-18 15:00', tip: '未佩戴安全帽12' },
])
const { proxy } = getCurrentInstance();
const { violation_level_type } = toRefs(proxy?.useDict('violation_level_type'));
const swiperContent = ref<HTMLDivElement>()
const props = defineProps({
isHide: {
type: Boolean,
default: false
},
projectId: {
type: String,
default: ""
}
})
const inspectionList = ref([{
id: "",
label: "",
picture: "",
createTime: ""
}])
const swiperContent = ref()
const swiperItemWidth = ref(100)
const canLeft = ref(false)
const canRight = ref(true)
const swiperClick = (direction: 'left' | 'right') => {
const swiperClick = (direction) => {
if (!swiperContent.value) return
if (direction === 'right') {
if (swiperContent.value.scrollLeft >= swiperContent.value.scrollWidth - swiperContent.value.clientWidth) {
@ -70,10 +79,79 @@ const swiperClick = (direction: 'left' | 'right') => {
}
swiperContent.value.scrollLeft -= swiperItemWidth.value
}
// 更新箭头状态
canLeft.value = swiperContent.value.scrollLeft > 0
canRight.value = swiperContent.value.scrollLeft < swiperContent.value.scrollWidth - swiperContent.value.clientWidth
}
const getInspectionList = async () => {
const res = await getScreenSafetyInspection(props.projectId)
const { code, data } = res
if (code === 200) {
data.map(item => {
item.label = violation_level_type.value.find((i) => i.value === item.violationType)?.label
})
inspectionList.value = data
}
}
// 创建地球
const createEarth = () => {
window.YJ.on({
ws: true,
// host: getIP(), //资源所在服务器地址
// username: this.loginForm.username, //用户名 可以不登录(不填写用户名),不登录时无法加载服务端的数据
// password: md5pass, //密码 生成方式md5(用户名_密码)
}).then((res) => {
let earth = new YJ.YJEarth("earth");
window.Earth1 = earth;
YJ.Global.openRightClick(window.Earth1);
YJ.Global.openLeftClick(window.Earth1);
let view = {
"position": {
"lng": 102.03643298211526,
"lat": 34.393586474501,
"alt": 11298179.51993155
},
"orientation": {
"heading": 360,
"pitch": -89.94481747201486,
"roll": 0
}
}
loadBaseMap(earth.viewer)
YJ.Global.CesiumContainer(window.Earth1, {
compass: false, //罗盘
});
// YJ.Global.flyTo(earth, view);
// YJ.Global.setDefaultView(earth.viewer, view)
})
}
// 加载底图
const loadBaseMap = (viewer) => {
// 创建瓦片提供器
const imageryProvider = new Cesium.UrlTemplateImageryProvider({
url: 'https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
// 可选:设置瓦片的格式
fileExtension: 'png',
// 可选:设置瓦片的范围和级别
minimumLevel: 0,
maximumLevel: 18,
// 可选设置瓦片的投影默认为Web Mercator
projection: Cesium.WebMercatorProjection,
// 可选:如果瓦片服务需要跨域请求,设置请求头部
credit: new Cesium.Credit('卫星图数据来源')
});
// 添加图层到视图
const layer = viewer.imageryLayers.addImageryProvider(imageryProvider);
}
onMounted(() => {
swiperItemWidth.value = swiperContent.value.children[0].clientWidth + 20
getInspectionList()
createEarth()
if (swiperContent.value && swiperContent.value.children.length > 0) {
swiperItemWidth.value = swiperContent.value.children[0].clientWidth + 20
}
})
</script>
@ -82,24 +160,41 @@ onMounted(() => {
.centerPage {
display: flex;
flex-direction: column;
width: 50vw;
height: 100%;
}
.topPage,
.endPage {
display: flex;
flex-direction: column;
align-items: center;
width: 100%;
padding: 15px 0;
border: 1px solid rgba(29, 214, 255, 0.1);
box-sizing: border-box;
}
.topPage,
.endPage {
display: flex;
flex-direction: column;
align-items: center;
justify-content: center;
width: 100%;
padding: 15px 0;
border: 1px solid rgba(230, 247, 255, 0.1);
box-sizing: border-box;
}
.topPage {
flex: 1;
margin-bottom: 23px;
}
.topPage {
flex: 1;
margin-bottom: 23px;
transition: flex 0.5s ease;
}
.endPage {
max-height: 300px;
opacity: 1;
transition: all 0.5s ease;
}
/* 向下滑出动画 */
.slide-out-down {
transform: translateY(100%);
opacity: 0;
max-height: 0;
padding: 0;
margin: 0;
border: none;
}
.swiper {
@ -161,14 +256,20 @@ onMounted(() => {
.arrow {
display: grid;
place-items: center;
width: 20px;
height: 20px;
width: 24px;
height: 24px;
border-radius: 50%;
border: 1px solid skyblue;
border: 1px solid rgba(29, 214, 255, 0.3);
color: skyblue;
cursor: pointer;
transition: all 0.3s ease;
&:canUse {
color: #000 !important;
&.canUse {
border: 1px solid rgba(29, 214, 255, 1);
}
&:hover:not(.canUse) {
opacity: 0.7;
}
}
</style>

View File

@ -6,7 +6,7 @@
</div>
<div style="font-size: 12px; padding-left: 10px">安全生产天数</div>
<div class="header_left_text">
1,235
{{ safetyDay }}
<span style="font-size: 12px"></span>
</div>
</div>
@ -14,16 +14,18 @@
<div>XXX智慧工地管理平台</div>
<div>XXX Smart Construction Stic Management Dashboard</div>
</div>
<div class="right">
<div class="header_right">
<div class="top-bar">
<!-- 左侧天气图标 + 日期文字 -->
<div class="left-section">
<img src="@/assets/large/weather.png" alt="天气图标" />
<span>
<span>多云 9°/18°</span>
<span style="padding-left: 20px"> {{ week[date.week] }} ({{ date.ymd }})</span>
</span>
<div class="weather-list" @mouseenter="requestPause" @mouseleave="resumeScroll">
<div v-for="(item, i) in weatherList" :key="i" class="weather-item"
:style="{ transform: `translateY(-${offsetY}px)`, transition: transition }">
<img :src="`../../../src/assets/images/${item.icon}.png`" alt="" />
<div>{{ item.weather }}{{ item.tempMin }}°/{{ item.tempMax }}°</div>
<div>{{ item.week }}({{ item.date }})</div>
</div>
</div>
</div>
<!-- 分割线 -->
<div class="divider">
@ -35,48 +37,155 @@
<img src="@/assets/large/setting.png" alt="设置图标" />
<span>管理系统</span>
</div>
<!-- 分割线 -->
<div class="divider">
<div class="top-block"></div>
<div class="bottom-block"></div>
</div>
<!-- -->
<div class="change" @click="emit('changePage')">
<el-icon size="20" v-if="!isFull">
<Expand />
</el-icon>
<el-icon size="20" v-else>
<Fold />
</el-icon>
</div>
</div>
</div>
</div>
</template>
<script setup lang="ts">
const week = ['星期日', '星期一', '星期二', '星期三', '星期四', '星期五', '星期六'];
const date = ref({
ymd: '',
hms: '',
week: 0
import { ref, onMounted, onUnmounted } from 'vue';
import { getScreenSafetyDay, getScreenWeather } from '@/api/projectScreen';
interface Weather {
week: string;
date: string;
icon: string;
weather: string;
tempMax: string;
tempMin: string;
}
const props = defineProps({
projectId: {
type: String,
default: ''
},
isFull: {
type: Boolean,
default: false
}
})
const emit = defineEmits(['changePage'])
const safetyDay = ref<number>(0);
const weatherList = ref<Weather[]>([])
const timer = ref<number | null>(0)
const offsetY = ref<number>(0)
const curIndex = ref(0)
const transition = ref('transform 0.5s ease');
const pendingPause = ref(false);
/**
* 判断当前时间是白天/夜晚
*/
function judgeDayOrNight(sunRise: string, sunSet: string) {
// 将 "HH:MM" 格式转为分钟数(便于计算)
const timeToMinutes = (timeStr: any) => {
const [hours, minutes] = timeStr.split(':').map(Number);
return isNaN(hours) || isNaN(minutes) ? 0 : hours * 60 + minutes;
};
// 转换日出、日落时间为分钟数
const sunRiseMinutes = timeToMinutes(sunRise);
const sunSetMinutes = timeToMinutes(sunSet);
// 获取当前时间并转为分钟数
const now = new Date();
const currentMinutes = now.getHours() * 60 + now.getMinutes();
// true 白天 false 夜晚
return currentMinutes >= sunRiseMinutes && currentMinutes <= sunSetMinutes
? true
: false;
}
/**
* 设置天气周期滑动
*/
const setWeatherScroll = () => {
curIndex.value += 1
transition.value = 'transform 0.3s ease';
offsetY.value = curIndex.value * 60
if (curIndex.value === weatherList.value.length - 1) {
setTimeout(() => {
transition.value = 'none';
curIndex.value = 0;
offsetY.value = 0;
}, 350);
}
}
function startScroll() {
if (timer.value) clearInterval(timer.value);
timer.value = window.setInterval(setWeatherScroll, 5000);
}
function requestPause() {
if (timer.value) {
clearInterval(timer.value)
timer.value = null
}
pendingPause.value = true;
}
function resumeScroll() {
console.log('resumeScroll')
pendingPause.value = false;
startScroll();
}
onMounted(() => {
/**
* 获取安全生产天数
*/
getScreenSafetyDay(props.projectId).then(res => {
const { data, code } = res
if (code === 200) {
safetyDay.value = data.safetyDay;
}
})
/**
* 获取近三天天气
*/
getScreenWeather(props.projectId).then(res => {
const { data, code } = res
if (code === 200) {
data.forEach(item => {
if (judgeDayOrNight(item.sunRise, item.sunSet)) {
item.weather = item.dayStatus
item.icon = item.dayIcon
} else {
item.weather = item.nightStatus
item.icon = item.nightIcon
}
})
weatherList.value = data
// 多添加第一项 实现无缝衔接
weatherList.value = [...weatherList.value, weatherList.value[0]]
startScroll()
}
})
});
const setTime = () => {
let date1 = new Date();
let year: any = date1.getFullYear();
let month: any = date1.getMonth() + 1;
let day: any = date1.getDate();
let hours: any = date1.getHours();
if (hours < 10) {
hours = '0' + hours;
}
let minutes: any = date1.getMinutes();
if (minutes < 10) {
minutes = '0' + minutes;
}
let seconds: any = date1.getSeconds();
if (seconds < 10) {
seconds = '0' + seconds;
}
date.value.ymd = year + '-' + month + '-' + day;
date.value.hms = hours + ':' + minutes + ':' + seconds;
date.value.week = date1.getDay();
};
// 添加定时器,每秒更新一次时间
const timer = setInterval(setTime, 1000);
// 组件卸载时清除定时器
onUnmounted(() => {
clearInterval(timer);
});
if (timer.value) {
clearInterval(timer.value)
}
})
</script>
<style scoped lang="scss">
@ -107,6 +216,12 @@ onUnmounted(() => {
}
}
.header_right {
width: 100%;
height: 100%;
display: flex;
}
.title {
color: #fff;
font-family: 'AlimamaShuHeiTi', sans-serif;
@ -124,12 +239,6 @@ onUnmounted(() => {
font-size: 14px;
}
.right {
width: 100%;
height: 100%;
display: flex;
}
/* 顶部栏容器Flex 水平布局 + 垂直居中 */
.top-bar {
width: 100%;
@ -137,7 +246,6 @@ onUnmounted(() => {
display: flex;
align-items: center;
justify-content: flex-end;
// background-color: #1e2128;
color: #fff;
padding: 8px 16px;
font-size: 14px;
@ -145,24 +253,37 @@ onUnmounted(() => {
/* 左侧区域(天气 + 日期):自身也用 Flex 水平排列,确保元素在一行 */
.left-section {
height: 100%;
display: flex;
align-items: center;
// margin-right: auto; /* 让右侧元素(管理系统)居右 */
}
.left-section img {
width: 32px;
height: 32px;
margin-right: 8px;
/* 图标与文字间距 */
.weather-list {
height: 60px;
overflow: hidden;
.weather-item {
height: 60px;
line-height: 60px;
display: flex;
align-items: center;
&>div:last-child {
margin-left: 10px;
}
img {
width: 50px;
height: 50px;
}
}
}
}
/* 分割线(视觉分隔,可根据需求调整样式) */
.divider {
display: grid;
grid-template-rows: 1fr 1fr;
height: 100%;
/* 根据需要调整高度 */
gap: 2px;
padding: 14px 10px;
}
@ -195,4 +316,11 @@ onUnmounted(() => {
margin-right: 6px;
/* 图标与文字间距 */
}
.change {
display: grid;
place-items: center;
margin-right: 10px;
cursor: pointer;
}
</style>

View File

@ -2,30 +2,53 @@
<div class="leftPage">
<div class="topPage">
<Title title="项目公告" />
<div class="content">
<div class="content_item" v-for="item in 6" :key="item">
<div class="round">
<div class="sub_round"></div>
<div class="content_item" v-for="item in news" :key="item.id">
<img src="@/assets/projectLarge/round.svg" alt="">
<div class="ellipsis">
{{ item.title }}
<span @click="showNewsDetail(item)" style="color: rgba(138, 149, 165, 1);">{{ item.id === newId ? '关闭' :
'查看' }}</span>
</div>
<div class="ellipsis">2025年6月23日 重庆市两江新区广场前期准备与审批完毕区广场前期准备与审批完毕前期准备与审批完毕区广场前期准备与审批完毕</div>
</div>
</div>
</div>
<div class="detailBox" :class="{ 'show': newId }">
<!-- <div class="detail_title">{{ newDetail.title }}</div> -->
<div class="detail_content" v-html="newDetail.content"></div>
</div>
<div class="endPage">
<Title title="人员情况" />
<div class="map">
<img src="@/assets/projectLarge/map.svg" alt="">
<!-- <div ref="mapChartRef"></div> -->
</div>
<div class="attendance_tag">
<div class="tag_item" v-for="(item, index) in tagList" :key="index">
<div class="tag_item">
<img src="@/assets/projectLarge/people.svg" alt="">
<div class="tag_title">{{ item.title }}</div>
<div class="tag_title">出勤人</div>
<div class="tag_info">
{{ item.number }}
<span style="font-size: 14px;">{{ index === 2 ? '%' : '人' }}</span>
{{ attendanceCount }}
<span style="font-size: 14px;"></span>
</div>
</div>
<div class="tag_item">
<img src="@/assets/projectLarge/people.svg" alt="">
<div class="tag_title">在岗人</div>
<div class="tag_info">
{{ peopleCount }}
<span style="font-size: 14px;"></span>
</div>
</div>
<div class="tag_item">
<img src="@/assets/projectLarge/people.svg" alt="">
<div class="tag_title">出勤率</div>
<div class="tag_info">
{{ attendanceRate }}
<span style="font-size: 14px;">%</span>
</div>
</div>
</div>
@ -37,11 +60,12 @@
<div class="attendance_item_title">出勤率</div>
<div class="attendance_item_title">出勤时间</div>
</div>
<div v-for="item in list" :key="item.title" class="attendance_item">
<div class="attendance_item_title">{{ item.title }}</div>
<div class="attendance_item_number">{{ item.number }} <span class="subfont">/{{ item.number }}</span></div>
<div v-for="item in teamAttendanceList" :key="item.id" class="attendance_item">
<div class="attendance_item_title">{{ item.teamName }}</div>
<div class="attendance_item_number">{{ item.attendanceNumber }} <span class="subfont">/{{ item.allNumber
}}</span></div>
<div class="attendance_item_rate">{{ item.attendanceRate }} %</div>
<div class="attendance_item_date subfont">{{ item.date }}</div>
<div class="attendance_item_date subfont">{{ item.attendanceTime }}</div>
</div>
</div>
</div>
@ -51,29 +75,101 @@
<script setup lang="ts">
import { ref } from "vue"
import Title from './title.vue'
import { getScreenNews, getScreenPeople } from '@/api/projectScreen';
import { mapOption } from './optionList'
import * as echarts from 'echarts';
const list = ref([
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
{ title: '智慧系统运维', number: 30, attendanceRate: 100, date: '2025-08-05 08:10' },
const props = defineProps({
projectId: {
type: String,
default: ''
}
})
let mapChart = null
const mapChartRef = ref<HTMLDivElement | null>(null);
const news = ref([])
const newDetail = ref({
title: '',
content: ''
})
const newId = ref('')
const attendanceCount = ref(0)
const attendanceRate = ref(0)
const peopleCount = ref(0)
const teamAttendanceList = ref([
{ id: "", teamName: "", attendanceNumber: 0, allNumber: 0, attendanceRate: 0, attendanceTime: "" },
])
const tagList = ref([
{ title: '出勤人数', number: 259 },
{ title: '在岗人数', number: 100 },
{ title: '出勤率', number: 100 },
])
/**
* 显示新闻详情
*/
const showNewsDetail = (item: any) => {
if (newId.value === item.id) {
newId.value = ''
return
}
newDetail.value = item
newId.value = item.id
}
/**
* 获取项目人员出勤数据
*/
const getPeopleData = async () => {
const res = await getScreenPeople(props.projectId);
const { data, code } = res
if (code === 200) {
attendanceCount.value = data.attendanceCount
attendanceRate.value = data.attendanceRate
peopleCount.value = data.peopleCount
teamAttendanceList.value = data.teamAttendanceList
}
}
/**
* 获取项目新闻数据
*/
const getNewsData = async () => {
const res = await getScreenNews(props.projectId);
const { data, code } = res
if (code === 200) {
news.value = data
}
}
/**
* 初始化地图
*/
const initMapChart = () => {
if (!mapChartRef.value) {
return;
}
mapChart = echarts.init(mapChartRef.value);
mapChart.setOption(mapOption);
}
onMounted(() => {
// nextTick(() => {
// initMapChart();
// });
getPeopleData()
getNewsData()
})
onUnmounted(() => {
// if (mapChart) {
// mapChart.dispose();
// mapChart = null;
// }
});
</script>
<style scoped lang="scss">
.leftPage {
display: flex;
flex-direction: column;
width: calc(25vw - 30px);
margin: 0 15px;
height: 100%;
.topPage,
@ -115,11 +211,11 @@ const tagList = ref([
display: flex;
align-items: flex-start;
gap: 10px;
// position: relative;
margin-bottom: 20px;
font-size: 14px;
font-weight: 400;
color: rgba(230, 247, 255, 1);
cursor: pointer;
.ellipsis {
display: -webkit-box;
@ -134,21 +230,8 @@ const tagList = ref([
margin-bottom: 0;
}
.round {
display: grid;
place-items: center;
img {
margin-top: 3px;
width: 12px;
height: 12px;
border-radius: 50%;
background: rgba(29, 214, 255, 0.3);
.sub_round {
width: 6px;
height: 6px;
border-radius: 50%;
background: #1DD6FF;
}
}
}
}
@ -165,12 +248,13 @@ const tagList = ref([
margin-top: 15px;
.tag_item {
width: 28%;
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
border: 1px dashed rgba(29, 214, 255, 0.3);
padding: 10px 25px;
padding: 10px;
.tag_info {
font-size: 20px;
@ -201,4 +285,45 @@ const tagList = ref([
.subfont {
color: rgba(138, 149, 165, 1);
}
.detailBox {
position: absolute;
left: 20vw;
top: 0;
width: 300px;
height: 300px;
max-height: 500px;
overflow-y: auto;
padding: 0 15px;
box-sizing: border-box;
background: rgba(255, 255, 255, 0.2);
border-radius: 4px;
transition: all 0.3s ease;
opacity: 0;
z-index: -1;
&.show {
left: 25vw;
opacity: 1;
z-index: 1;
}
}
.detailBox::before {
content: '';
/* 绝对定位相对于父元素 */
position: absolute;
/* 定位到左侧中间位置 */
left: -10px;
top: 50%;
/* 垂直居中 */
transform: translateY(-50%);
/* 利用边框创建三角形 */
border-width: 10px 10px 10px 0;
border-style: solid;
/* 三角形颜色与背景匹配,左侧边框透明 */
border-color: transparent rgba(255, 255, 255, 0.2) transparent transparent;
/* 确保三角形在内容下方 */
z-index: -1;
}
</style>

View File

@ -0,0 +1,153 @@
export let pieOption = {
// 定义中心文字
graphic: [
{
type: 'text',
left: 'center',
top: '40%',
style: {
// 需要从接口替换
text: '70%',
fontSize: 24,
fontWeight: 'bold',
fill: '#fff'
}
},
{
type: 'text',
left: 'center',
top: '50%',
style: {
text: '总进度',
fontSize: 14,
fill: '#fff'
}
},
],
legend: {
show: true,
type: 'plain',
bottom: 20,
itemWidth: 12,
itemHeight: 12,
textStyle: {
color: '#fff'
}
},
series: {
type: 'pie',
data: [],
radius: [50, 80],
center: ['50%', '45%'],
itemStyle: {
borderColor: '#fff',
borderWidth: 1
},
label: {
alignTo: 'edge',
formatter: '{name|{b}}\n{percent|{c} %}',
minMargin: 10,
edgeDistance: 20,
lineHeight: 15,
rich: {
name: {
fontSize: 12,
color: '#fff'
},
percent: {
fontSize: 12,
color: '#fff'
}
}
},
legend: {
top: 'bottom'
},
}
};
export let barOption = {
legend: {
icon: 'rect',
itemWidth: 12,
itemHeight: 12,
// 调整文字与图标间距
data: ['计划流转面积', '已流转面积'],
top: 0,
right: 20,
textStyle: {
color: '#fff',
}
},
xAxis: {
type: 'category',
data: ['地块1', '地块2', '地块3', '地块4', '地块5', '地块6'],
axisLabel: {
color: '#fff'
},
axisLine: {
show: false
},
splitLine: {
show: false
}
},
yAxis: {
name: '单位m²',
type: 'value',
axisLabel: {
formatter: '{value}'
}
},
grid: {
bottom: 0, // 距离容器底部的距离
containLabel: true // 确保坐标轴标签不被裁剪
},
series: [
{
name: '计划流转面积',
type: 'bar',
data: [],
barWidth: '20%',
itemStyle: {
color: 'rgb(29, 253, 253)'
},
},
{
name: '已流转面积',
type: 'bar',
data: [],
barWidth: '20%',
itemStyle: {
color: 'rgb(25, 181, 251)'
},
}
]
};
export let mapOption = {
geo: {
map: 'ch',
roam: true,
aspectScale: Math.cos((47 * Math.PI) / 180),
},
series: [
{
type: 'graph',
coordinateSystem: 'geo',
data: [
{ name: 'a', value: [7.667821250000001, 46.791734269956265] },
{ name: 'b', value: [7.404848750000001, 46.516308805996054] },
{ name: 'c', value: [7.376673125000001, 46.24728858538375] },
{ name: 'd', value: [8.015320625000001, 46.39460918238572] },
{ name: 'e', value: [8.616400625, 46.7020608630855] },
{ name: 'f', value: [8.869981250000002, 46.37539345234199] },
{ name: 'g', value: [9.546196250000001, 46.58676648282309] },
{ name: 'h', value: [9.311399375, 47.182454114178896] },
{ name: 'i', value: [9.085994375000002, 47.55395822835779] },
{ name: 'j', value: [8.653968125000002, 47.47709530818285] },
{ name: 'k', value: [8.203158125000002, 47.44506909144329] }
],
}
]
};

View File

@ -2,21 +2,14 @@
<div class="leftPage">
<div class="topPage">
<Title title="项目概况" />
<div class="content">
<div class="content_item">项目名称智慧生态工地社区开发项目</div>
<div class="content_item">项目位置贵州省贵阳市乌当区具体地块编号01-123-11</div>
<div class="content_item">占地面积约10000亩</div>
<div class="content_item"> 土地性质城镇住宅用地兼容商业用地容积率2.5</div>
</div>
<div class="content" v-html="generalize"></div>
</div>
<div class="endPage">
<!-- 饼图容器 -->
<Title title="形象进度" />
<div ref="pieChartRef" class="echart" />
<!-- 折线图容器 -->
<div ref="lineChartRef" class="echart" />
<div class="chart_container">
<div ref="pieChartRef" class="echart" />
<div ref="lineChartRef" class="echart" />
</div>
</div>
</div>
</template>
@ -25,149 +18,45 @@
import { ref, onMounted, onUnmounted, nextTick } from "vue"
import Title from './title.vue'
import * as echarts from 'echarts';
import { pieOption, barOption } from './optionList';
import { getScreenLand, getScreenImgProcess, getScreenGeneralize } from '@/api/projectScreen';
const props = defineProps({
projectId: {
type: String,
default: 0
}
})
const generalize = ref()
// 饼图相关
const pieChartRef = ref<HTMLDivElement | null>(null);
let pieChart: any = null;
const totalPercent = ref(0)
// 折线图相关
const lineChartRef = ref<HTMLDivElement | null>(null);
let lineChart: any = null;
// 土地数据 折线图
const designAreaData = ref([])
const transferAreaData = ref([])
// 饼图数据
const pieData = [
{ name: '桩点浇筑', value: 13 },
{ name: '水泥灌注', value: 7 },
{ name: '箱变安装', value: 40 },
{ name: '支架安装', value: 20 },
{ name: '组件安装', value: 20 },
{ label: 'areaPercentage', name: '厂区', value: 0 },
{ label: 'roadPercentage', name: '道路', value: 0 },
{ label: 'collectorLinePercentage', name: '集电线路', value: 0 },
{ label: 'exportLinePercentage', name: '送出线路', value: 0 },
{ label: 'substationPercentage', name: '升压站', value: 0 },
{ label: 'boxTransformerPercentage', name: '箱变', value: 0 },
]
// 折线图数据
const barData = {
xAxis: ['地块1', '地块2', '地块3', '地块4', '地块5', '地块6'],
series: [
{
name: '计划流转面积',
data: [70, 25, 45, 115, 70, 85]
},
{
name: '已流转面积',
data: [105, 30, 150, 65, 80, 200]
}
]
}
// 饼图配置
const pieOption = {
series: {
type: 'pie',
data: pieData,
radius: [50, 80],
itemStyle: {
borderColor: '#fff',
borderWidth: 1
},
label: {
alignTo: 'edge',
formatter: '{name|{b}}\n{percent|{c} %}',
minMargin: 10,
edgeDistance: 20,
lineHeight: 15,
rich: {
name: {
fontSize: 12,
color: '#fff'
},
percent: {
fontSize: 12,
color: '#fff'
}
}
},
legend: {
top: 'bottom'
},
}
};
// 柱状图配置
const barOption = {
legend: {
data: ['计划流转面积', '已流转面积'],
top: 0
},
grid: {
left: '3%',
right: '4%',
bottom: '3%',
containLabel: true
},
xAxis: {
type: 'category',
data: barData.xAxis
},
yAxis: {
name: '单位m²',
type: 'value',
axisLabel: {
formatter: '{value}'
}
},
series: [
{
type: 'bar',
data: [], // 空数据仅用于承载markArea
markArea: {
silent: true, // 背景不响应交互
data: (() => {
const groupCount = 3; // 共3组6个月 ÷ 2
const groupWidth = 1.8; // 每组背景宽度覆盖2根柱子
const bgData = [];
for (let i = 0; i < groupCount; i++) {
const startX = i * 2 - 0.9; // 每组起始位置
const endX = startX + groupWidth; // 每组结束位置
bgData.push([
{ xAxis: startX, yAxis: 0 },
{ xAxis: endX, yAxis: 100 }
]);
}
return bgData;
})(),
itemStyle: {
color: 'rgba(255, 255, 255, 0.05)',
borderRadius: 4
}
}
},
{
name: '计划流转面积',
type: 'bar',
data: barData.series[0].data,
barWidth: 15, // 柱形宽度
itemStyle: {
color: 'rgb(29, 253, 253)'
},
},
{
name: '已流转面积',
type: 'bar',
data: barData.series[1].data,
barWidth: 15,
itemStyle: {
color: '#rgb(25, 181, 251)'
},
}
]
};
// 初始化饼图
const initPieChart = () => {
if (!pieChartRef.value) {
console.error('未找到饼图容器元素');
return;
}
pieOption.series.data = pieData
pieOption.graphic[0].style.text = totalPercent.value + '%'
pieChart = echarts.init(pieChartRef.value, null, {
renderer: 'canvas',
useDirtyRect: false
@ -181,6 +70,8 @@ const initLineChart = () => {
console.error('未找到折线图容器元素');
return;
}
barOption.series[0].data = designAreaData.value
barOption.series[1].data = transferAreaData.value
lineChart = echarts.init(lineChartRef.value, null, {
renderer: 'canvas',
useDirtyRect: false
@ -194,11 +85,52 @@ const handleResize = () => {
if (lineChart) lineChart.resize();
};
/**
* 获取项目土地统计数据
*/
const getScreenLandData = async () => {
const res = await getScreenLand(props.projectId);
const { data, code } = res
if (code === 200) {
designAreaData.value = data.map((item: any) => Number(item.designArea))
transferAreaData.value = data.map((item: any) => Number(item.transferArea))
initLineChart();
}
}
/**
* 获取项目形象进度数据
*/
const getScreenImgProcessData = async () => {
const res = await getScreenImgProcess(props.projectId);
const { data, code } = res
if (code === 200) {
totalPercent.value = data.totalPercentage
pieData.forEach((item: any) => {
item.value = data[item.label]
})
initPieChart()
}
}
/**
* 获取项目概况数据
*/
const getScreenGeneralizeData = async () => {
const res = await getScreenGeneralize(props.projectId);
const { data, code } = res
if (code === 200) {
generalize.value = data
}
}
// 组件挂载时初始化图表
onMounted(() => {
getScreenLandData()
getScreenImgProcessData()
getScreenGeneralizeData()
nextTick(() => {
initPieChart();
initLineChart();
window.addEventListener('resize', handleResize);
});
});
@ -221,8 +153,6 @@ onUnmounted(() => {
.leftPage {
display: flex;
flex-direction: column;
width: calc(25vw - 30px);
margin: 0 15px;
height: 100%;
.topPage,
@ -240,15 +170,38 @@ onUnmounted(() => {
flex: 1;
margin-top: 23px;
.echart {
.chart_container {
display: flex;
flex-direction: column;
gap: 5px;
width: 100%;
height: 100%;
}
.echart {
height: 48%;
width: 100%;
}
}
}
.content {
margin: 10px 35px;
height: 100px;
margin: 0 15px;
padding: 0 10px;
margin-top: 15px;
box-sizing: border-box;
overflow-y: auto;
&::-webkit-scrollbar-track {
background: rgba(204, 204, 204, 0.1);
border-radius: 10px;
}
&::-webkit-scrollbar-thumb {
background: rgba(29, 214, 255, 0.78);
border-radius: 10px;
}
.content_item {
font-size: 14px;
@ -262,12 +215,6 @@ onUnmounted(() => {
}
}
.ellipse {
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.subfont {
color: rgba(138, 149, 165, 1);
}

View File

@ -4,8 +4,8 @@
<img src="@/assets/projectLarge/section.svg" alt="">
<img src="@/assets/projectLarge/border.svg" alt="">
</div>
<div v-if="prefix">
<img src="@/assets/projectLarge/robot.svg" alt="" style="width: 20px; height: 20px;margin-right: 5px;">
<div>
<slot></slot>
</div>
<div>{{ title }}</div>
</div>

View File

@ -1,45 +1,108 @@
<template>
<div class="large-screen">
<Header />
<div class="large_screen">
<Header :projectId="projectId" :isFull="isFull" @changePage="handleChangePage" />
<div class="nav">
<leftPage />
<centerPage />
<rightPage />
<div class="nav_left" :style="{ left: isHideOther ? '-25vw' : '0' }">
<leftPage :projectId="projectId" />
</div>
<div class="nav_center" :style="{ width: isFull ? '100%' : 'calc(50vw - 30px)' }">
<centerPage :projectId="projectId" :isHide="isFull" />
</div>
<div class="nav_right" :style="{ right: isHideOther ? '-25vw' : '0' }">
<rightPage :projectId="projectId" />
</div>
</div>
</div>
</template>
<script setup lang="ts">
import { ref } from 'vue';
import Header from './components/header.vue';
import leftPage from './components/leftPage.vue';
import centerPage from './components/centerPage.vue';
import rightPage from './components/rightPage.vue';
import { useUserStoreHook } from '@/store/modules/user';
const userStore = useUserStoreHook();
const projectId = computed(() => userStore.selectedProject.id);
const isFull = ref(false)
const isHideOther = ref(false)
/**
* 切换中心页面全屏
*/
const handleChangePage = () => {
if (isFull.value) {
isFull.value = false;
setTimeout(() => {
isHideOther.value = false;
}, 500);
} else {
isFull.value = true;
isHideOther.value = true;
}
}
</script>
<style lang="scss" scoped>
.large-screen {
.large_screen {
width: 100vw;
height: 100vh;
background: url('@/assets/large/bg.png') no-repeat;
background-size: 100% 100%;
background-attachment: fixed;
background-color: rgba(4, 7, 17, 1);
overflow: hidden;
}
.nav {
display: flex;
gap: 15rpx;
width: 100%;
height: calc(100vh - 100px);
position: relative;
display: grid;
place-items: center;
width: calc(100vw - 30px);
height: calc(100vh - 90px);
margin: 0 auto;
box-sizing: border-box;
color: #fff;
}
.nav_left,
.nav_right {
margin: 0 15px 15px 15px;
position: absolute;
width: calc(25vw - 15px);
height: 100%;
transition: all 0.5s ease;
}
.nav_left {
top: 0;
left: 0;
}
.nav_right {
top: 0;
right: 0;
}
.nav_center {
margin-bottom: 15px;
height: 100%;
transition: all 0.5s ease;
}
/* 中间面板全屏动画 */
.full-width {
/* 取消flex增长使用固定宽度 */
width: calc(100vw - 30px);
flex: none;
}
.slide_left {
left: -25vw;
opacity: 0;
}
.slide_right {
right: -25vw;
opacity: 0;
}
</style>

View File

@ -5,7 +5,7 @@
<el-form :model="queryForm" :inline="true">
<el-form-item label="版本号" prop="versions">
<el-select v-model="queryForm.versions" placeholder="选择版本号" @change="changeVersions">
<el-option v-for="item in options" :key="item.id" :label="item.versions" :value="item.id" />
<el-option v-for="item in options" :key="item.id" :label="item.versions" :value="item.versions" />
</el-select>
</el-form-item>
<el-form-item label="表名" prop="sheet">
@ -58,9 +58,10 @@
<el-card shadow="never" class="mb8">
<el-table ref="tableRef" v-loading="loading" :data="tableData" row-key="id" border lazy default-expand-all>
<el-table-column prop="num" label="编号" />
<el-table-column prop="name" label="工程或费用名称" />
<el-table-column prop="unit" label="单位" />
<el-table-column prop="quantity" label="数量" />
<el-table-column prop="name" label="工程或费用名称" />
<el-table-column prop="unit" label="单位" align="center" />
<el-table-column prop="quantity" label="数量" align="center" />
<el-table-column prop="specification" label="规格" align="center" />
<el-table-column prop="remark" label="单价" align="center">
<template #default="scope">
<span>{{ scope.row.unitPrice }}</span>
@ -166,7 +167,8 @@ const getTableData = async () => {
const params = {
projectId: currentProject.value?.id,
sheet: queryForm.value.sheet,
versions: queryForm.value.versions
versions: queryForm.value.versions,
type: '1'
};
const res = await getTreeLimit(params);
loading.value = false;
@ -209,8 +211,6 @@ const tableRef = ref<any>();
const toggleExpandAll = () => {
isExpandAll.value = !isExpandAll.value;
console.log(isExpandAll.value);
tableData.value.forEach((row) => {
tableRef.value.toggleRowExpansion(row, isExpandAll.value);
});
@ -248,7 +248,7 @@ const handleExport = () => {
projectId: currentProject.value?.id,
sheet: queryForm.value.sheet
},
`限价一览表${queryForm.value.sheet}.xlsx`
`投标成本核算清单${queryForm.value.sheet}.xlsx`
);
};
// 审核

View File

@ -210,7 +210,8 @@ const getListTable = async () => {
const res = await getTreeLimit({
projectId: currentProject.value?.id,
versions: form.value.versions,
sheet: form.value.sheet
sheet: form.value.sheet,
type: '0'
});
if (res.code == 200) {
tableData.value = res.data;

View File

@ -5,7 +5,7 @@
<el-form :model="queryForm" :inline="true">
<el-form-item label="版本号" prop="versions">
<el-select v-model="queryForm.versions" placeholder="选择版本号" @change="changeVersions">
<el-option v-for="item in options" :key="item.id" :label="item.versions" :value="item.id" />
<el-option v-for="item in options" :key="item.id" :label="item.versions" :value="item.versions" />
</el-select>
</el-form-item>
<el-form-item label="表名" prop="sheet">
@ -57,10 +57,11 @@
</transition>
<el-card shadow="never" class="mb8">
<el-table ref="tableRef" v-loading="loading" :data="tableData" row-key="id" border lazy default-expand-all>
<el-table-column prop="num" label="编号" />
<el-table-column prop="name" label="工程或费用名称" />
<el-table-column prop="unit" label="单位" />
<el-table-column prop="quantity" label="数量">
<el-table-column prop="num" label="编号" align="center" />
<el-table-column prop="name" label="工程或费用名称" align="center" />
<el-table-column prop="unit" label="单位" align="center" />
<el-table-column prop="specification" label="规格" align="center" />
<el-table-column prop="quantity" label="数量" align="center">
<template #default="scope">
{{ scope.row.children.length > 0 ? '' : scope.row.quantity }}
</template>
@ -184,7 +185,8 @@ const getTableData = async () => {
const params = {
projectId: currentProject.value?.id,
sheet: queryForm.value.sheet,
versions: queryForm.value.versions
versions: queryForm.value.versions,
type: '0'
};
const res = await getTreeLimit(params);
loading.value = false;
@ -260,7 +262,7 @@ const importExcel = (options: any): any => {
let formData = new FormData();
formData.append('file', options.file);
loading.value = true;
BiddingImportExcelFile({ projectId: currentProject.value?.id }, formData)
BiddingImportExcelFile({ projectId: currentProject.value?.id, versions: queryForm.value.versions }, formData)
.then((res) => {
const { code } = res;
if (code == 200) {
@ -288,7 +290,7 @@ const handleExport = () => {
projectId: currentProject.value?.id,
sheet: queryForm.value.sheet
},
`限价一览表${queryForm.value.sheet}.xlsx`
`投标成本核算${queryForm.value.sheet}.xlsx`
);
};
// 审核

View File

@ -23,13 +23,19 @@
{{ (queryParams.pageNum - 1) * queryParams.pageSize + scope.$index + 1 }}
</template>
</el-table-column>
<el-table-column prop="name" label="名称" />
<el-table-column prop="content" label="内容" />
<el-table-column prop="price" label="限价" />
<el-table-column prop="name" label="名称" align="center" />
<el-table-column prop="content" label="内容" align="center" />
<el-table-column prop="price" label="限价" align="center" />
<el-table-column prop="plannedBiddingTime" align="center">
<template #header> <span style="color: red">*</span>计划招标时间 </template>
<template #default="scope">
<el-date-picker v-model="scope.row.plannedBiddingTime" type="date" value-format="YYYY-MM-DD" placeholder="选择时间" />
<el-date-picker
v-model="scope.row.plannedBiddingTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="选择时间"
:disabled="scope.row.status == 0"
/>
</template>
</el-table-column>
<el-table-column prop="price" label="操作" align="center">
@ -138,9 +144,16 @@
<el-table-column prop="useQuantity" label="剩余量" align="center">
<template #default="scope">
{{
(scope.row.quantity ? Number(scope.row.quantity) : 0) - (scope.row.useQuantity ? Number(scope.row.useQuantity) : 0) == 0
(scope.row.quantity ? Number(scope.row.quantity) : 0) -
(scope.row.useQuantity ? Number(scope.row.useQuantity) : 0) -
(scope.row.selectNum ? Number(scope.row.selectNum) : 0) ==
0
? ''
: (scope.row.quantity ? Number(scope.row.quantity) : 0) - (scope.row.useQuantity ? Number(scope.row.useQuantity) : 0)
: (
(scope.row.quantity ? Number(scope.row.quantity) : 0) -
(scope.row.selectNum ? Number(scope.row.selectNum) : 0) -
(scope.row.useQuantity ? Number(scope.row.useQuantity) : 0)
).toFixed(2)
}}
</template>
</el-table-column>
@ -149,12 +162,16 @@
<el-table-column prop="price" label="总价" align="center">
<template #default="scope">
{{
((scope.row.quantity ? Number(scope.row.quantity) : 0) - (scope.row.useQuantity ? Number(scope.row.useQuantity) : 0)) *
((scope.row.quantity ? Number(scope.row.quantity) : 0) -
(scope.row.useQuantity ? Number(scope.row.useQuantity) : 0) -
(scope.row.selectNum ? Number(scope.row.selectNum) : 0)) *
Number(scope.row.unitPrice) ==
0
? ''
: (
((scope.row.quantity ? Number(scope.row.quantity) : 0) - (scope.row.useQuantity ? Number(scope.row.useQuantity) : 0)) *
((scope.row.quantity ? Number(scope.row.quantity) : 0) -
(scope.row.useQuantity ? Number(scope.row.useQuantity) : 0) -
(scope.row.selectNum ? Number(scope.row.selectNum) : 0)) *
Number(scope.row.unitPrice)
).toFixed(2)
}}
@ -328,18 +345,10 @@ const getVersionNums = async () => {
getSheetName();
} else {
treeForm.value.versions = '';
ElMessage({
message: '获取版本号失败',
type: 'warning'
});
}
}
} catch (error) {
console.log(error);
ElMessage({
message: '获取版本号失败',
type: 'warning'
});
}
};
//获取表名
@ -356,19 +365,11 @@ const getSheetName = async () => {
treeForm.value.sheet = res.data[0];
} else {
treeForm.value.sheet = '';
ElMessage({
message: '获取表名失败',
type: 'warning'
});
}
getTreeList();
}
} catch (error) {
console.log(error);
ElMessage({
message: '获取表名失败',
type: 'warning'
});
}
};
const handleSelection = (selection: any) => {

View File

@ -60,8 +60,10 @@
<el-table ref="tableRef" v-loading="loading" :data="tableData" row-key="id" border lazy default-expand-all>
<el-table-column prop="num" label="编号" />
<el-table-column prop="name" label="工程或费用名称" />
<el-table-column prop="unit" label="单位" />
<el-table-column prop="quantity" label="数量">
<el-table-column prop="unit" label="单位" align="center" />
<el-table-column prop="taxRate" label="税率" align="center" />
<el-table-column prop="specification" label="规格" align="center" />
<el-table-column prop="quantity" label="数量" align="center">
<template #default="scope">
{{ scope.row.children.length > 0 ? '' : scope.row.quantity }}
</template>
@ -84,7 +86,7 @@
/>
</template>
</el-table-column>
<el-table-column prop="price" label="总价" align="center">
<el-table-column prop="price" label="总价" align="center" >
<template #default="scope">
<!-- {{ scope.row.children.length > 0 ? scope.row.children.reduce((sum, child) => sum + child.price, 0) : scope.row.price }} -->
{{ scope.row.price != 0 ? Number(scope.row.price).toFixed(2) : null }}
@ -124,7 +126,7 @@ const loading = ref(false);
const options = ref<any[]>([]);
const sheets = ref<any[]>([]);
const tableData = ref<any[]>([]);
const isExpandAll = ref(false);
const isExpandAll = ref(true);
const reviewStatus = ref('');
const versionObj: any = ref({});
const versionMap = new Map();
@ -150,18 +152,10 @@ const getVersionNums = async () => {
getSheetName();
} else {
queryForm.value.versions = '';
ElMessage({
message: '获取版本号失败',
type: 'warning'
});
}
}
} catch (error) {
console.log(error);
ElMessage({
message: '获取版本号失败',
type: 'warning'
});
}
};
//选择版本号
@ -193,19 +187,11 @@ const getSheetName = async () => {
queryForm.value.sheet = res.data[0];
} else {
queryForm.value.sheet = '';
ElMessage({
message: '获取表名失败',
type: 'warning'
});
}
getTableData();
}
} catch (error) {
console.log(error);
ElMessage({
message: '获取表名失败',
type: 'warning'
});
}
};
//获取表格
@ -323,7 +309,7 @@ const handleExport = () => {
versions: queryForm.value.versions,
type: '1'
},
`限价一览${queryForm.value.sheet}.xlsx`
`限价一览${queryForm.value.sheet}.xlsx`
);
};
// 审批

View File

@ -93,9 +93,7 @@
</el-row>
<el-form-item label="附图">
<file-Upload v-model="form.attachmentsImg" :file-type="['pdf', 'png', 'jpg', 'jpeg', 'gif', 'bmp']">
<el-button type="primary">上传附件</el-button>
</file-Upload>
<file-Upload :fileSize="50" v-model="form.attachmentsImg" :file-type="['png', 'jpg', 'jpeg']"> </file-Upload>
</el-form-item>
<!-- 变更原因 -->
<el-form-item label="变更原因">
@ -114,7 +112,7 @@
<el-input v-model="form.content" type="textarea" :rows="6" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="附件" prop="attachments">
<file-upload v-model="form.attachments" :limit="1" :file-type="['pdf']"></file-upload>
<file-upload :fileSize="50" v-model="form.attachments" :limit="1" :file-type="['pdf']"></file-upload>
</el-form-item>
<el-form-item label="变更费用估算" prop="costEstimation">
<el-input v-model="form.costEstimation" :rows="6" type="number" placeholder="请输入变更费用估算" />

View File

@ -24,7 +24,8 @@
</el-form-item>
<el-form-item label="合同类型" prop="contractType">
<el-select v-model="form.contractType" placeholder="请选择合同类型">
<el-option v-for="item in income_contract_type" :key="item.value" :label="item.label" :value="item.value" />
<el-option v-for="item in income_contract_type" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="业主单位" prop="contractOwner">
@ -37,17 +38,15 @@
<editor v-model="form.contractedContent" :min-height="192" />
</el-form-item>
<el-form-item label="合同金额" prop="amount">
<el-input
v-model="form.amount"
placeholder="请输入合同金额"
oninput="value=value.replace(/[^0-9.]/g,'').replace(/\.{2,}/g,'.').replace(/^(\-)*(\d+)\.(\d\d).*$/,'$1$2.$3')"
/>
<el-input v-model="form.amount" placeholder="请输入合同金额"
oninput="value=value.replace(/[^0-9.]/g,'').replace(/\.{2,}/g,'.').replace(/^(\-)*(\d+)\.(\d\d).*$/,'$1$2.$3')" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="附件">
<FileUpload :multiple="true" :fileType="['pdf']" :onUploadSuccess="onUploadSuccess" :ref="fileRef" :defaultFileList="tempFileList" />
<FileUpload :multiple="true" :fileType="['pdf']" :onUploadSuccess="onUploadSuccess" :ref="fileRef"
:defaultFileList="tempFileList" />
</el-form-item>
</el-form>
</template>
@ -61,35 +60,36 @@
</el-form-item>
<el-form-item label="合同类型" prop="contractType">
<el-select v-model="form.contractType" placeholder="请选择合同类型">
<el-option v-for="item in expenses_contract_type" :key="item.value" :label="item.label" :value="item.value" />
<el-option v-for="item in expenses_contract_type" :key="item.value" :label="item.label"
:value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="招标计划" prop="tenderId">
<!-- <el-input v-model="form.tenderId" placeholder="请输入招标Id" /> -->
<el-input v-model="form.name" placeholder="请选择招标计划" disabled />
<el-button type="primary" @click="handleChoose" v-hasPermi="['ctr:expensesContract:tenderList']">选择招标</el-button>
<el-button type="primary" @click="handleChoose"
v-hasPermi="['ctr:expensesContract:tenderList']">选择招标</el-button>
</el-form-item>
<el-form-item label="供应商" prop="contractSupplier">
<el-input v-model="form.contractSupplier" placeholder="请输入供应商" disabled />
</el-form-item>
<el-form-item label="分包内容">
<!-- <editor v-model="form.contractedContent" :min-height="192" disabled /> -->
<el-input v-model="form.contractedContent" style="width: 300px" :autosize="{ minRows: 2, maxRows: 4 }" type="textarea" disabled />
<el-input v-model="form.contractedContent" style="width: 300px" :autosize="{ minRows: 2, maxRows: 4 }"
type="textarea" disabled />
</el-form-item>
<el-form-item label="合同金额" prop="amount">
<el-input
oninput="value=value.replace(/[^0-9.]/g,'').replace(/\.{2,}/g,'.').replace(/^(\-)*(\d+)\.(\d\d).*$/,'$1$2.$3')"
v-model="form.amount"
placeholder="请输入合同金额"
disabled
/>
v-model="form.amount" placeholder="请输入合同金额" disabled />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="合同附件">
<FileUpload :multiple="true" :fileType="['pdf']" :onUploadSuccess="onUploadSuccess" :ref="fileRef" :defaultFileList="tempFileList" />
<FileUpload :multiple="true" :fileType="['pdf']" :onUploadSuccess="onUploadSuccess" :ref="fileRef"
:defaultFileList="tempFileList" />
</el-form-item>
</el-form>
</template>
@ -208,7 +208,7 @@ const payRatioComputed = computed({
return total;
},
// 只读
set: () => {}
set: () => { }
});
const checkContractType = (type) => {
contract_type.value = type;
@ -280,7 +280,6 @@ const resetForm = () => {
};
fileList.value = [];
tempFileList.value = [];
contract_type.value = '';
setTimeout(() => {
localStorage.removeItem('tempContractForm');
}, 0);
@ -399,6 +398,7 @@ const getInfoByProjectIdList = async () => {
form.value.contractOwner = res.data.planDuration;
};
onMounted(() => {
contract_type.value = '';
getInfoByProjectIdList();
const tempForm = localStorage.getItem('tempContractForm');

View File

@ -47,9 +47,9 @@
<el-row :gutter="8" class="mb-3 font-medium text-gray-700 whitespace-nowrap">
<el-col :span="4">专业</el-col>
<el-col :span="5">设计人员可多选</el-col>
<el-col :span="5">校审人员</el-col>
<el-col :span="5">校审人员</el-col> <el-col :span="4">审核人员</el-col>
<el-col :span="5">审定人员</el-col>
<el-col :span="4">审核人员</el-col>
<el-col :span="3"></el-col>
</el-row>
@ -65,7 +65,7 @@
>
<el-row :gutter="8" class="items-top">
<!-- 1. 专业选择核心统一所有角色的专业来源 -->
<el-col :span="3" class="mb-4 sm:mb-0 pl-4" style="margin-top:8px;">
<el-col :span="3" class="mb-4 sm:mb-0 pl-4" style="margin-top: 8px">
<el-form-item
:prop="`designers.${configIndex}.userMajor`"
:rules="[
@ -76,7 +76,8 @@
label-width="60px"
label="专业"
>
<el-select filterable
<el-select
filterable
v-model="form.designers[configIndex].userMajor"
placeholder="请选择专业"
class="w-full transition-all duration-300 border-gray-300"
@ -109,7 +110,8 @@
label="设计"
label-width="50px"
>
<el-select filterable
<el-select
filterable
v-model="person.userId"
placeholder="选择人员"
class="w-full transition-all duration-300 border-gray-300"
@ -165,7 +167,8 @@
label="校审"
label-width="50px"
>
<el-select filterable
<el-select
filterable
v-model="person.userId"
placeholder="选择人员"
class="w-full transition-all duration-300 border-gray-300"
@ -206,7 +209,65 @@
</el-col>
<!-- 4. 审定人员 -->
<!-- 5. 审核人员 -->
<el-col :span="5" class="mb-4 sm:mb-0">
<div class="pl-2 border-l-2 border-purple-200 py-2">
<div class="space-y-3">
<div
v-for="(person, personIndex) in majorConfig.auditorPersons"
:key="`auditor-${configIndex}-${personIndex}`"
class="flex items-center"
>
<el-form-item
:prop="`auditor.${configIndex}.persons.${personIndex}.userId`"
:rules="{ required: true, message: '请选择审核人员', trigger: 'change' }"
class="flex-1 mr-2 mb-0"
label="审核"
label-width="50px"
>
<el-select
filterable
v-model="person.userId"
placeholder="选择人员"
class="w-full transition-all duration-300 border-gray-300"
@change="() => checkDuplicate(person, 'auditor', configIndex, personIndex)"
>
<el-option v-for="item in userList" :key="`user-${item.userId}`" :label="item.nickName" :value="item.userId" />
</el-select>
</el-form-item>
<!-- <div class="flex gap-1">
<el-button
type="danger"
size="small"
@click="removePerson('auditor', configIndex, personIndex)"
class="transition-all duration-300 hover:bg-red-600"
:disabled="majorConfig.auditorPersons.length <= 1 || disabledForm"
>
<el-icon :size="14"><Delete /></el-icon>
</el-button>
<el-button
type="success"
size="small"
@click="addPerson('auditor', configIndex)"
class="transition-all duration-300 transform hover:scale-105"
:disabled="!form.designers[configIndex].userMajor || disabledForm"
>
<el-icon :size="14"><Plus /></el-icon>
</el-button>
</div> -->
</div>
</div>
<div
v-if="majorConfig.auditorPersons.length == 0"
class="text-gray-500 text-xs py-2 bg-gray-100 rounded border border-dashed border-gray-200"
>
点击"添加"
</div>
</div>
</el-col>
<el-col :span="5" class="mb-4 sm:mb-0">
<div class="pl-2 border-l-2 border-orange-200 py-2">
<div class="space-y-3">
<div
@ -221,7 +282,8 @@
label="审定"
label-width="50px"
>
<el-select filterable
<el-select
filterable
v-model="person.userId"
placeholder="选择人员"
class="w-full transition-all duration-300 border-gray-300"
@ -261,64 +323,8 @@
</div>
</el-col>
<!-- 5. 审核人员 -->
<el-col :span="5" class="mb-4 sm:mb-0">
<div class="pl-2 border-l-2 border-purple-200 py-2">
<div class="space-y-3">
<div
v-for="(person, personIndex) in majorConfig.auditorPersons"
:key="`auditor-${configIndex}-${personIndex}`"
class="flex items-center"
>
<el-form-item
:prop="`auditor.${configIndex}.persons.${personIndex}.userId`"
:rules="{ required: true, message: '请选择审核人员', trigger: 'change' }"
class="flex-1 mr-2 mb-0"
label="审核"
label-width="50px"
>
<el-select filterable
v-model="person.userId"
placeholder="选择人员"
class="w-full transition-all duration-300 border-gray-300"
@change="() => checkDuplicate(person, 'auditor', configIndex, personIndex)"
>
<el-option v-for="item in userList" :key="`user-${item.userId}`" :label="item.nickName" :value="item.userId" />
</el-select>
</el-form-item>
<!-- <div class="flex gap-1">
<el-button
type="danger"
size="small"
@click="removePerson('auditor', configIndex, personIndex)"
class="transition-all duration-300 hover:bg-red-600"
:disabled="majorConfig.auditorPersons.length <= 1 || disabledForm"
>
<el-icon :size="14"><Delete /></el-icon>
</el-button>
<el-button
type="success"
size="small"
@click="addPerson('auditor', configIndex)"
class="transition-all duration-300 transform hover:scale-105"
:disabled="!form.designers[configIndex].userMajor || disabledForm"
>
<el-icon :size="14"><Plus /></el-icon>
</el-button>
</div> -->
</div>
</div>
<div
v-if="majorConfig.auditorPersons.length == 0"
class="text-gray-500 text-xs py-2 bg-gray-100 rounded border border-dashed border-gray-200"
>
点击"添加"
</div>
</div>
</el-col>
<!-- 操作列 -->
<el-col :span="2" class="text-right pr-4">
<el-col :span="2" class="pr-4 mt-2 text-right">
<el-button
type="text"
class="text-red-500 hover:text-red-700 transition-colors"
@ -338,13 +344,17 @@
type="primary"
size="large"
v-hasPermi="['design:user:batch']"
icon="Check"
@click="submitForm"
class="px-8 py-2.5 transition-all duration-300 transform hover:scale-105 bg-blue-500 hover:bg-blue-600 text-white font-medium"
>
<i class="el-icon-check mr-2"></i>确认提交
>确认提交
</el-button>
<el-button size="large" @click="resetForm" class="px-8 py-2.5 transition-all duration-300 border-gray-300 hover:bg-gray-100 font-medium">
<i class="el-icon-refresh mr-2"></i>重置
<el-button
size="large"
icon="Refresh"
@click="resetForm"
class="px-8 py-2.5 transition-all duration-300 border-gray-300 hover:bg-gray-100 font-medium"
>重置
</el-button>
</div>
</el-form>

View File

@ -6,7 +6,7 @@
<el-card v-if="index < 3" shadow="always">
<el-form :model="state.queryForm" :inline="true">
<el-form-item label="版本号" prop="versions">
<el-select v-model="state.queryForm.versions" placeholder="选择版本号">
<el-select v-model="state.queryForm.versions" placeholder="选择版本号" @change="handleChangeVersion">
<el-option v-for="item in state.options" :key="item.versions" :label="item.versions" :value="item.versions" />
</el-select>
</el-form-item>
@ -16,7 +16,7 @@
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="openTable(index)">{{ isExpandAll ? '一键收起' : '一键展开' }}</el-button>
<el-button type="primary" @click="openTable(index)">{{ isExpandAll ? '一键收起' : '一键展开' }}</el-button>
</el-form-item>
<el-form-item>
<el-button type="success" @click="downloadTemplate(1)">下载模板</el-button>
@ -62,15 +62,10 @@
</el-upload>
</el-form-item>
<el-form-item v-if="state.versionsData.status == 'draft'">
<el-button type="primary" con="edit" @click="clickApprovalSheet()">审核</el-button>
<el-button type="primary" con="edit" @click="clickApprovalSheet()">审核</el-button>
</el-form-item>
<el-form-item v-if="state.versionsData.status == 'waiting' || state.versionsData.status == 'finish'">
<el-button
icon="view"
@click="lookApprovalFlow()"
type="warning"
>查看流程</el-button
>
<el-button icon="view" @click="lookApprovalFlow()" type="warning">查看流程</el-button>
</el-form-item>
</el-form>
</el-card>
@ -171,6 +166,7 @@ const handleTabChange = (tab) => {
onMounted(async () => {
await getVersionNums();
});
// 获取版本号
async function getVersionNums(isSheet = true) {
try {
@ -247,10 +243,10 @@ async function handleSheetName() {
// 获取列表
async function handleQueryList(isSheet = true) {
if (isSheet && !state.queryForm.sheet) {
console.warn('表名不存在,无法获取列表数据');
return;
}
// if (isSheet && !state.queryForm) {
// console.warn('表名不存在,无法获取列表数据');
// return;
// }
try {
state.loading.list = true;
@ -308,12 +304,12 @@ function handleChange(sheet) {
function handleChangeVersion(versions) {
state.queryForm.versions = versions;
state.versionsData = state.options.find((e) => e.versions == versions);
console.log('state.versionsData', state.versionsData);
// console.log('state.versionsData', state.versionsData);
state.sheets = [];
handleQueryList();
}
// 在 openTable 方法中通过索引获取对应的表格实例
function openTable( index) {
function openTable(index) {
isExpandAll.value = !isExpandAll.value;
nextTick(() => {
// 通过索引获取当前标签页的表格实例
@ -358,12 +354,12 @@ function lookApprovalFlow(row) {
const downloadTemplate = (type) => {
// 导出模版文件
try {
let linkurl = '';
let linkurl = '';
let name = '';
if (type==1) {
if (type == 1) {
linkurl = '/billOfQuantities.xlsx';
name = '工程量清单模板.xlsx';
}else{
} else {
linkurl = '/materialsEquipment.xlsx';
name = '物资设备清单模板.xlsx';
}

View File

@ -16,7 +16,7 @@
<!-- 表单区域 -->
<el-card class="rounded-lg shadow-sm bg-white border border-gray-100 transition-all hover:shadow-md overflow-hidden">
<div class="p-4 bg-gradient-to-r from-blue-50 to-indigo-50 border-b border-gray-100">
<h3 class="text-lg font-semibold text-gray-800">投标工程清单</h3>
<h3 class="text-lg font-semibold text-gray-800">投标工程清单</h3>
</div>
<div class="p-6">
<el-form

View File

@ -113,7 +113,7 @@ const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>();
//按钮组件
const flowCodeOptions = [
{
value: currentProject.value?.id + '_materialsPlans',
value: currentProject.value?.id + '_equipmentList',
label: '物资设备清单审核'
}
];

View File

@ -93,11 +93,10 @@
>审核通知单</el-button
>
<el-button
v-if="scope.row.status != 'draft'"
v-if="scope.row.status != 'draft' || scope.row.auditStatus != 'draft'"
type="success"
link
icon="View"
v-hasPermi="['design:designChange:query']"
@click="handleViewDetail(scope.row)"
>查看通知单</el-button
>
@ -113,8 +112,7 @@
type="warning"
link
icon="View"
v-hasPermi="['design:drawingreviewReceipts:list']"
v-if="scope.row.status != 'draft'"
v-if="scope.row.status != 'draft' || scope.row.auditStatus != 'draft'"
@click="handleViewHistory(scope.row)"
>查看单据</el-button
>

View File

@ -50,7 +50,7 @@
<div class="p-4">
<p class="text-gray-600 mb-4">请选择要启动的流程</p>
<el-select v-model="flowCode" placeholder="请选择流程" style="width: 100%">
<el-option v-for="item in flowCodeOptions" :key="item.value" :label="item.label" :value="item.value" />
<el-option v-for="item in [flowCodeOptions[optionIndex]]" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</div>
<template #footer>
@ -98,6 +98,7 @@ const flowCodeOptions = ref([
label: '资金设计变更审批'
}
]);
const optionIndex = ref<number>(0);
const flowCode = ref<string>('');
const status = ref<string>('');
@ -272,12 +273,12 @@ const submit = async (status, data) => {
} else {
if ((form.value.status === 'draft' && (flowCode.value === '' || flowCode.value === null)) || routeParams.value.type === 'add') {
if (form.value.costEstimation == '0') {
flowCodeOptions.value = [flowCodeOptions.value[0]];
optionIndex.value = 0;
} else {
console.log('🚀 ~ submit ~ flowCodeOptions.value:', flowCodeOptions.value[1]);
flowCodeOptions.value = [flowCodeOptions.value[1]];
optionIndex.value = 1;
}
flowCode.value = flowCodeOptions.value[0].value;
flowCode.value = flowCodeOptions.value[optionIndex.value].value;
dialogVisible.visible = true;
return;
}

View File

@ -49,7 +49,32 @@
<!-- 资料文件区域 -->
<div class="mb-8">
<div class="flex items-center justify-between mb-5">
<h3 class="text-lg font-semibold text-blue-700">资料文件清单</h3>
<div style="display: flex; align-items: center">
<h3 class="text-lg font-semibold text-blue-700" style="margin-right: 20px">资料文件清单</h3>
<el-upload
class="upload-excel"
action="#"
v-if="!form.id || form.status == 'draft'"
ref="uploadRef"
:auto-upload="false"
:on-change="importTemplate"
:show-file-list="false"
:accept="'.xlsx,.xls'"
:limit="1"
>
<el-button type="primary" icon="Download">导入文件</el-button>
</el-upload>
<el-button
v-if="!form.id || form.status == 'draft'"
type="primary"
style="margin-left: 20px"
icon="Upload"
@click="exportTemplate"
class="transition-all hover:bg-blue-600"
>
导出模版
</el-button>
</div>
<el-button type="primary" size="small" @click="addDocumentItem" v-if="!disabledAll" icon="Plus" class="transition-all hover:bg-blue-600">
添加资料
</el-button>
@ -180,11 +205,11 @@ import { ref, reactive, computed, onMounted, onUnmounted, watch, getCurrentInsta
import { useUserStoreHook } from '@/store/modules/user';
import { ElMessage, ElLoading, FormRules } from 'element-plus';
import { systemUserList } from '@/api/design/appointment';
import { collectBatch, byProjectId, exportWord } from '@/api/design/received';
import { collectBatch, byProjectId, exportWord, exportExcel } from '@/api/design/received';
import { getUser } from '@/api/system/user';
import type { ComponentInternalInstance, ElFormInstance } from 'element-plus';
import { getInfo } from '@/api/login';
import * as XLSX from 'xlsx';
// 全局实例与状态管理
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStoreHook();
@ -200,7 +225,7 @@ const documentsFormRef = ref<ElFormInstance>();
const userList = ref<any[]>([]);
const userMap = new Map<string, string>(); // 存储用户ID与昵称映射
const disabledAll = ref(false); // 表单是否全部禁用
const uploadRef = ref<any>();
// 表单核心数据
const form = reactive({
projectId: currentProject.value?.id,
@ -445,7 +470,66 @@ const onLoad = async () => {
console.error('文件导出错误:', error);
}
};
const exportTemplate = async () => {
// 导出模版
proxy?.download(
'design/collect/exportExcel',
{
deptId: userStore.deptId
},
`收资清单表格.xlsx`
);
};
const importTemplate = async (files, fileList) => {
// 导入表格数据
const file = fileList[0].raw; // 获取原始文件对象
const reader = new FileReader();
let obj = {
id: '编码',
name: '人员',
fliename: '目录名',
remark: '备注'
};
reader.onload = (e) => {
try {
// 读取文件内容
const data = new Uint8Array(e.target.result);
// 解析Excel
const workbook = XLSX.read(data, { type: 'array' });
// 获取第一个工作表名称
const firstSheetName = workbook.SheetNames[0];
// 获取第一个工作表内容
const worksheet = workbook.Sheets[firstSheetName];
// 转换为JSON格式
const jsonData = XLSX.utils.sheet_to_json(worksheet);
if (jsonData.length === 0) {
ElMessage.info('Excel文件中没有数据');
return;
}
let arr = [];
// 判断form.documents 是否对象
jsonData.forEach((item, index) => {
if (item[obj.id]) {
arr.push({
id: Date.now() + index,
catalogueName: item[obj.fliename],
remark: item[obj.remark],
userId: item[obj.id]
});
}
});
form.documents = arr;
uploadRef.value.clearFiles();
console.log(arr);
} catch (err) {}
};
// 以ArrayBuffer方式读取文件
reader.readAsArrayBuffer(file);
};
/** 页面挂载初始化 */
onMounted(() => {
// 先获取当前用户信息,再获取部门用户列表,最后回显表单数据

View File

@ -121,10 +121,10 @@
</div> -->
<div class="info-item">
<span class="info-label">更新时间</span>
<span class="info-label">上传时间</span>
<div class="info-value mt-0.5 flex items-center">
<i class="fa fa-calendar-o text-gray-400 dark:text-gray-500 mr-1.5"></i>
{{ info.updateTime || '未更新' }}
{{ info.createTime || '暂无' }}
</div>
</div>
</div>

View File

@ -68,6 +68,7 @@
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['design:volumeCatalog:listFile']"
>查看文件</el-button
>
<el-badge v-if="scope.row.fileCount" :value="scope.row.fileCount" class="item" type="danger"> </el-badge>
</template>
</el-table-column>
<el-table-column label="外部意见" align="center">
@ -153,6 +154,11 @@
<el-form-item v-if="uploadForm.type == '3'" label="蓝图" prop="fileIds">
<file-upload :fileType="['pdf']" :isShowTip="false" :fileSize="100" v-model="uploadForm.fileIds"></file-upload>
</el-form-item>
<el-form-item v-if="uploadForm.type == '3'" label="通知人">
<el-select multiple filterable clearable v-model="form.userIds" placeholder="请选择通知人">
<el-option :value="item.userId" v-for="item in userCoryList" :key="item.userId" :label="item.nickName + '-' + item.phonenumber" />
</el-select>
</el-form-item>
<el-form-item v-if="uploadForm.type == '1'" label="过程图纸" prop="cancellationIds">
<file-upload :fileType="['pdf']" :isShowTip="false" :fileSize="100" v-model="uploadForm.cancellationIds"></file-upload>
</el-form-item>
@ -166,8 +172,8 @@
<!-- 查看文件列表 -->
<el-dialog draggable title="图纸列表" v-model="viewVisible" width="1000px">
<el-tabs type="border-card" v-model="activeName" class="demo-tabs" @tab-click="handleClick">
<el-tab-pane label="蓝图" name="3"
><TableContent :data="fileList" :wf-business-status="wf_business_status">
<el-tab-pane label="蓝图" name="3">
<TableContent :data="fileList" :wf-business-status="wf_business_status">
<template #operation="{ row }">
<el-button
link
@ -190,10 +196,10 @@
>查看单据</el-button
>
</template>
</TableContent></el-tab-pane
>
<el-tab-pane label="过程图纸 " name="1"
><TableContent :data="fileList" :wf-business-status="wf_business_status">
</TableContent>
</el-tab-pane>
<el-tab-pane label="过程图纸 " name="1">
<TableContent :data="fileList" :wf-business-status="wf_business_status">
<template #operation="{ row }">
<el-button link type="primary" icon="edit" @click="handleAudit(row)" v-if="row.auditStatus == 'draft' || row.auditStatus == 'back'"
>审核</el-button
@ -211,15 +217,15 @@
>查看单据</el-button
>
</template>
</TableContent></el-tab-pane
>
<el-tab-pane label="作废 " name="4"
><TableContent :data="fileList" :wf-business-status="wf_business_status">
</TableContent>
</el-tab-pane>
<el-tab-pane label="作废 " name="4">
<TableContent :data="fileList" :wf-business-status="wf_business_status">
<template #operation="{ row }">
<el-button type="danger" link icon="Download" @click="handleDownload(row)"> 下载 </el-button>
</template>
</TableContent></el-tab-pane
>
</TableContent>
</el-tab-pane>
</el-tabs>
<template #footer>
<span>
@ -255,7 +261,8 @@ import {
uploadVolumeFile,
majorList,
getVolumeCatafileList,
volumeFileList
volumeFileList,
copyUserList
} from '@/api/design/volumeCatalog';
import { VolumeCatalogVO } from '@/api/design/volumeCatalog/types';
import { useUserStoreHook } from '@/store/modules/user';
@ -281,6 +288,7 @@ const uploadOpinionVisible = ref(false);
const design = ref('');
const total = ref(0);
const dialogHistory = ref(false);
const userCoryList = ref([]);
const opinion = ref('');
const updateRow = ref({
opinion: []
@ -416,7 +424,13 @@ const cancel = () => {
reset();
dialog.visible = false;
};
// 获取人员列表
const getDesignUserList = async () => {
const res = await copyUserList({ projectId: currentProject.value?.id, userType: 2 });
if (res.code === 200) {
userCoryList.value = res.data;
}
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
@ -546,7 +560,7 @@ const onSubmit = async () => {
type: uploadForm.type
};
try {
await uploadVolumeFile(obj);
await uploadVolumeFile({ ...obj, userIds: form.value.userIds });
proxy?.$modal.msgSuccess('文件上传成功');
uploadVisible.value = false;
await getList();
@ -631,22 +645,29 @@ const getVolumeFileList = async (type) => {
}
};
const exportFile = () => {
// 导出模版文件
try {
// 创建a标签
const link = document.createElement('a');
// 设置PDF文件路径 - 相对于public目录
link.href = '/catalog.xlsx';
// 设置下载后的文件名
link.download = '设计出图计划导入模版.xlsx';
// 触发点击
document.body.appendChild(link);
link.click();
// 清理
document.body.removeChild(link);
} catch (error) {
alert('下载失败,请重试');
}
proxy?.download(
'/design/volumeCatalog/exportExcel',
{
projectId: currentProject.value?.id
},
`设计出图计划导入模版.xlsx`
);
// // 导出模版文件
// try {
// // 创建a标签
// const link = document.createElement('a');
// // 设置PDF文件路径 - 相对于public目录
// link.href = '/catalog.xlsx';
// // 设置下载后的文件名
// link.download = '设计出图计划导入模版.xlsx';
// // 触发点击
// document.body.appendChild(link);
// link.click();
// // 清理
// document.body.removeChild(link);
// } catch (error) {
// alert('下载失败,请重试');
// }
};
// 切换
const handleClick = (val) => {
@ -656,6 +677,7 @@ const handleAuditInfo = (row) => {
// 审核图纸
};
onMounted(() => {
getDesignUserList();
getSpecialtyList();
getList();
});
@ -666,6 +688,7 @@ const listeningProject = watch(
(nid, oid) => {
queryParams.value.projectId = nid;
form.value.projectId = nid;
getDesignUserList();
getSpecialtyList();
getList();
}
@ -684,6 +707,7 @@ onUnmounted(() => {
border-collapse: collapse; //合并为一个单一的边框
border-color: rgba(199, 199, 199, 1); //边框颜色按实际自定义即可
}
thead {
tr {
th {
@ -693,16 +717,19 @@ onUnmounted(() => {
letter-spacing: 5px;
padding: 15px;
}
td {
text-align: left;
height: 35px; //设置单元格最小高度
padding: 15px;
}
.th-bg {
background-color: rgba(247, 247, 247, 1);
}
}
}
tbody {
tr {
td {
@ -710,6 +737,7 @@ onUnmounted(() => {
height: 40px; //设置单元格最小高度
padding: 15px;
}
th {
height: 35px; //设置单元格最小高度
text-align: center;
@ -718,6 +746,7 @@ onUnmounted(() => {
}
}
}
.table-content {
box-shadow: 0px 0px 10px #ddd;
padding: 20px;

View File

@ -0,0 +1,236 @@
<template>
<div class="p5" style="width: 100%; height: calc(100vh - 84px)" v-loading="loading">
<!-- 返回按钮区域 -->
<div style="position: absolute; top: 20px; left: 20px; z-index: 100; display: flex; gap: 10px">
<el-button type="primary" icon="ArrowLeft" @click="handleBack">返回</el-button>
<el-button type="success" icon="Play" @click="handleStart">开始</el-button>
<el-button type="warning" icon="Pause" @click="handleStop">停止</el-button>
</div>
<div id="TrajectoryEarth" style="width: 100%; height: 100%"></div>
</div>
</template>
<script setup name="equipmentGPS">
import { ref, onMounted, onUnmounted } from 'vue';
import { ElButton } from 'element-plus';
import { ElMessage } from 'element-plus';
import { useRoute, useRouter } from 'vue-router'; // 补充导入useRouter
import { getFootNote } from '@/api/equipment/index';
import { ModelPathMover } from './index.js';
const route = useRoute();
const router = useRouter(); // 初始化router
const loading = ref(true);
let modelMover = null;
// 返回上一页
const handleBack = () => {
router.back();
};
// 开始轨迹运动
const handleStart = () => {
if (modelMover && typeof modelMover.start === 'function') {
try {
modelMover.start();
ElMessage.success('轨迹已开始播放');
} catch (error) {
console.error('启动轨迹失败:', error);
ElMessage.error('启动轨迹失败');
}
} else {
ElMessage.warning('轨迹未初始化,请稍后再试');
}
};
// 停止轨迹运动
const handleStop = () => {
if (modelMover && typeof modelMover.stop === 'function') {
try {
modelMover.stop();
ElMessage.success('轨迹已停止');
} catch (error) {
console.error('停止轨迹失败:', error);
ElMessage.error('停止轨迹失败');
}
} else {
ElMessage.warning('轨迹未初始化,请稍后再试');
}
};
let earthInstance = null;
let data = [
{
'locLongitude': 106.45637808828741,
'locLatitude': 29.5597535878972,
'locAltitude': 0
}
];
// 获取轨迹数据
const getTrajectoryData = async () => {
try {
// 从URL参数中获取clientId、projectId和userId
const { clientId, projectId, userId } = route.query;
if (!clientId || !projectId || !userId) {
ElMessage.warning('缺少必要参数,请检查传入的参数');
return;
}
loading.value = true;
const res = await getFootNote({ clientId, projectId, userId });
if (res && res.code === 200 && res.data && res.data.length > 0) {
data = res.data;
// 渲染轨迹
if (earthInstance && earthInstance.viewer) {
renderRange(data);
}
} else {
ElMessage.warning('暂无轨迹数据');
}
} catch (error) {
console.error('获取轨迹数据失败:', error);
ElMessage.error('获取轨迹数据失败,请稍后重试');
} finally {
loading.value = false;
}
};
// 创建地球
const createEarth = () => {
if (!window.YJ) {
ElMessage.error('YJ库未加载请检查依赖');
return;
}
window.YJ.on({
ws: true
// host: getIP(), // 资源所在服务器地址
// username: this.loginForm.username, // 用户名
// password: md5pass, // 密码
})
.then((res) => {
// 创建地球实例
earthInstance = new YJ.YJEarth('TrajectoryEarth');
window.Earth3 = earthInstance;
// 开启右键和左键点击事件
YJ.Global.openRightClick(window.Earth3);
YJ.Global.openLeftClick(window.Earth3);
// 设置初始视角
const view = {
position: {
lng: 102.03643298211526,
lat: 34.393586474501,
alt: 11298179.51993155
},
orientation: {
heading: 360,
pitch: -89.94481747201486,
roll: 0
}
};
YJ.Global.CesiumContainer(window.Earth3, {
compass: false //罗盘
});
// 加载底图
loadBaseMap(earthInstance.viewer);
// 可以取消注释以下代码来设置初始视角
// YJ.Global.flyTo(earthInstance, view);
// YJ.Global.setDefaultView(earthInstance.viewer, view)
// 地球创建完成后获取并渲染轨迹数据
getTrajectoryData();
})
.catch((err) => {
console.error('初始化地球失败:', err);
ElMessage.error('初始化地球失败,请稍后重试');
});
};
// 加载底图
const loadBaseMap = (viewer) => {
if (!viewer || !Cesium) {
ElMessage.error('Cesium库未加载请检查依赖');
return;
}
try {
// 创建瓦片提供器
const imageryProvider = new Cesium.UrlTemplateImageryProvider({
url: 'https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
fileExtension: 'png',
minimumLevel: 0,
maximumLevel: 18,
projection: Cesium.WebMercatorProjection,
credit: new Cesium.Credit('卫星图数据来源')
});
// 添加图层到视图
viewer.imageryLayers.addImageryProvider(imageryProvider);
} catch (err) {
console.error('加载底图失败:', err);
ElMessage.error('加载底图失败');
}
};
// 渲染轨迹
const renderRange = (data) => {
if (!data || data.length === 0) {
ElMessage.warning('无轨迹数据可渲染');
return;
}
if (!earthInstance || !earthInstance.viewer) {
ElMessage.error('地球实例未初始化');
return;
}
try {
const positions = data.map((item) => {
return {
lon: item.locLongitude,
lat: item.locLatitude,
height: item.locAltitude || 0,
name: item.name || ''
};
});
modelMover = new ModelPathMover(window.Earth3.viewer, {
// modelUrl: './air.glb', // 模型URL
positions: positions,
speed: 50, // 移动速度
loop: true, // 循环运动
iconUrl: '/image/Foot.png', // 模型上方图标
baseHeight: 10 // 外部传入的统一高度(米)
});
window.modelMover = modelMover;
} catch (err) {
console.error('渲染轨迹失败:', err);
ElMessage.error('渲染轨迹失败');
}
};
onMounted(() => {
createEarth();
});
onUnmounted(() => {
if (modelMover) {
modelMover.stop(); // 组件卸载时停止轨迹
modelMover = null;
}
if (earthInstance) {
earthInstance.destroy();
earthInstance = null;
window.Earth3 = null;
}
});
</script>
<style></style>

View File

@ -0,0 +1,392 @@
export class ModelPathMover {
/**
* 构造函数
* @param {Cesium.Viewer} viewer - Cesium viewer实例
* @param {Object} options - 配置选项
* @param {Number} [options.baseHeight=0] - 外部传入的统一高度(米),将叠加到所有位置点上
*/
constructor(viewer, options) {
this.viewer = viewer;
this.modelUrl = options.modelUrl;
this.positions = options.positions || [];
this.speed = options.speed || 10; // 米/秒
this.loop = options.loop !== undefined ? options.loop : true;
this.pathStyle = options.pathStyle || {
color: Cesium.Color.YELLOW,
width: 3
};
this.markerStyle = options.markerStyle || {
color: Cesium.Color.RED,
pixelSize: 10,
outlineColor: Cesium.Color.WHITE,
outlineWidth: 2
};
this.iconUrl = options.iconUrl;
this.iconSize = options.iconSize || 32;
this.iconOffset = options.iconOffset || 0;
this.headingOffset = options.headingOffset || 0;
// 新增接收外部传入的统一高度默认0米
this.baseHeight = options.baseHeight || 0;
// 内部状态(保持不变)
this.modelEntity = null;
this.pathEntity = null;
this.markerEntities = [];
this.iconEntity = null;
this.currentIndex = 0;
this.isMoving = false;
this.lastTime = 0;
this.animationFrameId = null;
this.currentPosition = null;
this.progress = 0;
this.modelHeight = 0;
// 初始化
this.init();
// 调试信息增加baseHeight打印
console.log('ModelPathMover初始化完成', {
positionCount: this.positions.length,
speed: this.speed,
loop: this.loop,
baseHeight: this.baseHeight // 打印传入的统一高度
});
}
/**
* 初始化(保持不变)
*/
init() {
if (this.positions.length < 2) {
console.warn('至少需要2个位置点才能进行移动');
}
this.createPath();
this.createMarkers();
this.createModel();
if (this.iconUrl) {
this.createIcon();
}
}
/**
* 创建模型修改叠加baseHeight
*/
createModel() {
if (this.positions.length === 0) return;
const firstPos = this.positions[0];
// 关键修改在原始高度基础上叠加baseHeight
const finalHeight = (firstPos.height || 0) + this.baseHeight;
this.currentPosition = Cesium.Cartesian3.fromDegrees(
firstPos.lon,
firstPos.lat,
finalHeight // 使用叠加后的高度
);
// 初始朝向计算(保持不变)
let initialOrientation = Cesium.Transforms.headingPitchRollQuaternion(this.currentPosition, new Cesium.HeadingPitchRoll(0, 0, 0));
if (this.positions.length > 1) {
const nextPos = this.positions[1];
// 计算下一个点时同样叠加baseHeight
const nextFinalHeight = (nextPos.height || 0) + this.baseHeight;
const nextCartesian = Cesium.Cartesian3.fromDegrees(nextPos.lon, nextPos.lat, nextFinalHeight);
const heading = this.calculateHeading(this.currentPosition, nextCartesian);
initialOrientation = Cesium.Transforms.headingPitchRollQuaternion(this.currentPosition, new Cesium.HeadingPitchRoll(heading, 0, 0));
}
this.modelEntity = this.viewer.entities.add({
name: 'Moving Model',
position: this.currentPosition,
orientation: initialOrientation,
model: {
uri: this.modelUrl,
minimumPixelSize: 64,
readyPromise: (model) => {
const boundingSphere = model.boundingSphere;
if (boundingSphere) {
this.modelHeight = boundingSphere.radius * 2;
console.log('模型高度检测完成:', this.modelHeight, '米');
}
}
}
});
}
/**
* 创建路径修改叠加baseHeight
*/
createPath() {
if (this.positions.length < 2) return;
// 关键修改遍历所有点时给每个点的高度叠加baseHeight
const pathPositions = this.positions.map((pos) => {
const finalHeight = (pos.height || 0) + this.baseHeight;
return Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, finalHeight);
});
this.pathEntity = this.viewer.entities.add({
name: 'Model Path',
polyline: {
positions: pathPositions,
width: this.pathStyle.width,
material: this.pathStyle.color,
clampToGround: false
}
});
}
/**
* 创建位置标记修改叠加baseHeight
*/
createMarkers() {
this.markerEntities = this.positions.map((pos, index) => {
// 关键修改标记点高度同样叠加baseHeight
const finalHeight = (pos.height || 0) + this.baseHeight;
const position = Cesium.Cartesian3.fromDegrees(pos.lon, pos.lat, finalHeight);
const marker = this.viewer.entities.add({
name: `Position Marker ${index}`,
position: position,
point: {
color: this.markerStyle.color,
pixelSize: this.markerStyle.pixelSize,
outlineColor: this.markerStyle.outlineColor,
outlineWidth: this.markerStyle.outlineWidth
},
label: {
text: pos.name || `Point ${index + 1}`,
font: '14px sans-serif',
pixelOffset: new Cesium.Cartesian2(0, -20),
fillColor: Cesium.Color.WHITE
}
});
return marker;
});
}
/**
* 创建模型上方的图标(保持不变)
*/
createIcon() {
this.iconEntity = this.viewer.entities.add({
name: 'Model Icon',
position: new Cesium.CallbackProperty(() => {
return this.adjustCartesianHeight(this.currentPosition, 0.3);
}, false),
billboard: {
image: this.iconUrl,
width: this.iconSize,
height: this.iconSize,
verticalOrigin: Cesium.VerticalOrigin.BOTTOM
},
label: {
text: 'Model Icon',
font: '14px sans-serif',
pixelOffset: new Cesium.Cartesian2(0, -40),
fillColor: Cesium.Color.WHITE
}
});
}
/**
* 调整笛卡尔坐标的高度(在原有高度基础上叠加指定值)
* @param {Cesium.Cartesian3} cartesian - 原始笛卡尔坐标
* @param {Number} heightOffset - 要增加的高度值(米,可为负数)
* @param {Cesium.Ellipsoid} [ellipsoid=Cesium.Ellipsoid.WGS84] - 参考椭球体
* @returns {Cesium.Cartesian3|null} 调整高度后的笛卡尔坐标失败返回null
*/
adjustCartesianHeight(cartesian, heightOffset, ellipsoid = Cesium.Ellipsoid.WGS84) {
// 1. 校验输入的笛卡尔坐标是否有效
// if (!Cesium.Cartesian3.isValid(cartesian)) {
// console.error("无效的笛卡尔坐标,无法调整高度");
// return null;
// }
// 2. 笛卡尔坐标 → Cartographic地理坐标含弧度经纬度和高度
const cartographic = Cesium.Cartographic.fromCartesian(cartesian, ellipsoid);
if (!cartographic) {
console.error('笛卡尔坐标转换为Cartographic失败');
return null;
}
// 3. 调整高度在原有高度上叠加offset可为负数
cartographic.height += heightOffset;
// 可选确保高度不低于0根据需求决定是否保留
// cartographic.height = Math.max(cartographic.height, 0);
// 4. Cartographic → 笛卡尔坐标(使用弧度经纬度转换)
const adjustedCartesian = Cesium.Cartesian3.fromRadians(
cartographic.longitude, // 经度(弧度)
cartographic.latitude, // 纬度(弧度)
cartographic.height, // 调整后的高度(米)
ellipsoid
);
return adjustedCartesian;
}
/**
* 开始移动(保持不变)
*/
start() {
if (this.isMoving) {
console.log('模型已经在移动中');
return;
}
if (this.positions.length < 2) {
console.error('无法移动位置点数量不足至少需要2个');
return;
}
this.isMoving = true;
this.lastTime = performance.now();
this.progress = 0;
console.log('开始移动,速度:', this.speed, '米/秒');
this.animate();
}
/**
* 动画循环函数(保持不变)
*/
animate() {
if (!this.isMoving) return;
const currentTime = performance.now();
const timeDeltaMs = currentTime - this.lastTime;
this.lastTime = currentTime;
const timeDelta = timeDeltaMs / 1000;
this.updatePosition(timeDelta);
this.animationFrameId = requestAnimationFrame(() => this.animate());
}
/**
* 停止移动(保持不变)
*/
stop() {
if (!this.isMoving) return;
this.isMoving = false;
console.log('停止移动');
if (this.animationFrameId) {
cancelAnimationFrame(this.animationFrameId);
this.animationFrameId = null;
}
}
/**
* 更新模型位置修改叠加baseHeight
*/
updatePosition(timeDelta) {
if (!this.isMoving) return;
const distanceToMove = this.speed * timeDelta;
const currentPos = this.positions[this.currentIndex];
const nextIndex = (this.currentIndex + 1) % this.positions.length;
const nextPos = this.positions[nextIndex];
// 关键修改计算当前点和下一点时均叠加baseHeight
const currentFinalHeight = (currentPos.height || 0) + this.baseHeight;
const nextFinalHeight = (nextPos.height || 0) + this.baseHeight;
const currentCartesian = Cesium.Cartesian3.fromDegrees(currentPos.lon, currentPos.lat, currentFinalHeight);
const nextCartesian = Cesium.Cartesian3.fromDegrees(nextPos.lon, nextPos.lat, nextFinalHeight);
// 后续逻辑保持不变
const totalDistance = Cesium.Cartesian3.distance(currentCartesian, nextCartesian);
if (totalDistance < 0.001) {
this.currentIndex = nextIndex;
this.progress = 0;
this.setModelPosition(nextCartesian, this.getNextPositionCartesian(nextIndex));
return;
}
this.progress += distanceToMove / totalDistance;
if (this.progress >= 1.0) {
const remainingDistance = (this.progress - 1.0) * totalDistance;
this.currentIndex = nextIndex;
if (!this.loop && this.currentIndex === this.positions.length - 1) {
this.setModelPosition(nextCartesian, null);
this.stop();
console.log('已到达终点,停止移动');
return;
}
const newTotalDistance = Cesium.Cartesian3.distance(nextCartesian, this.getNextPositionCartesian(this.currentIndex));
this.progress = newTotalDistance > 0.001 ? remainingDistance / newTotalDistance : 0;
this.setModelPosition(nextCartesian, this.getNextPositionCartesian(this.currentIndex));
} else {
const newPosition = Cesium.Cartesian3.lerp(currentCartesian, nextCartesian, this.progress, new Cesium.Cartesian3());
this.setModelPosition(newPosition, nextCartesian);
}
this.viewer.scene.requestRender();
}
/**
* 获取下一个位置的笛卡尔坐标修改叠加baseHeight
*/
getNextPositionCartesian(currentIndex) {
const nextIndex = (currentIndex + 1) % this.positions.length;
const nextPos = this.positions[nextIndex];
// 关键修改返回下一点时叠加baseHeight
const finalHeight = (nextPos.height || 0) + this.baseHeight;
return Cesium.Cartesian3.fromDegrees(nextPos.lon, nextPos.lat, finalHeight);
}
/**
* 设置模型位置和方向(保持不变)
*/
setModelPosition(position, targetPosition) {
if (!this.modelEntity) return;
this.currentPosition = position;
this.modelEntity.position.setValue(position);
if (targetPosition) {
const heading = this.calculateHeading(position, targetPosition);
this.modelEntity.orientation.setValue(Cesium.Transforms.headingPitchRollQuaternion(position, new Cesium.HeadingPitchRoll(heading, 0, 0)));
}
}
/**
* 计算两点之间的朝向(保持不变)
*/
calculateHeading(from, to) {
const fromCartographic = Cesium.Cartographic.fromCartesian(from);
const toCartographic = Cesium.Cartographic.fromCartesian(to);
const startLat = fromCartographic.latitude;
const startLon = fromCartographic.longitude;
const endLat = toCartographic.latitude;
const endLon = toCartographic.longitude;
const dLon = endLon - startLon;
const y = Math.sin(dLon) * Math.cos(endLat);
const x = Math.cos(startLat) * Math.sin(endLat) - Math.sin(startLat) * Math.cos(endLat) * Math.cos(dLon);
let heading = Math.atan2(y, x);
heading = (heading + Cesium.Math.TWO_PI) % Cesium.Math.TWO_PI;
heading = (heading + this.headingOffset) % Cesium.Math.TWO_PI;
return heading;
}
/**
* 手动设置模型高度(保持不变)
*/
setModelHeight(height) {
this.modelHeight = height;
console.log('手动设置模型高度:', height, '米');
}
/**
* 设置航向角偏移(保持不变)
*/
setHeadingOffset(offsetDegrees) {
this.headingOffset = Cesium.Math.toRadians(offsetDegrees);
console.log('设置航向角偏移:', offsetDegrees, '度');
}
/**
* 销毁所有资源(保持不变)
*/
destroy() {
this.stop();
if (this.modelEntity) this.viewer.entities.remove(this.modelEntity);
if (this.pathEntity) this.viewer.entities.remove(this.pathEntity);
this.markerEntities.forEach((marker) => this.viewer.entities.remove(marker));
if (this.iconEntity) this.viewer.entities.remove(this.iconEntity);
this.modelEntity = null;
this.pathEntity = null;
this.markerEntities = [];
this.iconEntity = null;
}
}

View File

@ -0,0 +1,827 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="设备标识" prop="clientId">
<el-input v-model="queryParams.clientId" placeholder="请输入设备标识" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="queryParams.deviceName" placeholder="请输入设备名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['gps:equipment:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['gps:equipment:remove']"
>删除</el-button
>
</el-col>
<el-col :span="1.8">
<el-button type="primary" plain icon="User" :disabled="single" @click="handleBindUser()" v-hasPermi="['gps:equipment:bindManmachine']"
>绑定用户</el-button
>
</el-col>
<el-col :span="2">
<el-button type="primary" plain @click="handleViewAll">{{ viewAllButtonText }}</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="equipmentList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="项目名称" align="center" prop="projectName" />
<el-table-column label="用户名称" align="center" prop="userName" />
<el-table-column label="设备标识" align="center" prop="clientId" />
<!-- 绑定状态列 -->
<el-table-column label="绑定状态" align="center">
<template #default="scope">
<el-tag :type="scope.row.type === 1 ? 'success' : 'warning'" size="small">
{{ scope.row.type === 1 ? '已绑定' : '未绑定' }}
</el-tag>
</template>
</el-table-column>
<!-- 设备名称列 -->
<el-table-column label="设备名称" align="center" prop="deviceName" />
<el-table-column label="远程连接地址" align="center" prop="remoteAddressStr" />
<el-table-column label="连接创建时间" align="center">
<template #default="scope">
{{ formatDateTime(scope.row.creationTime) }}
</template>
</el-table-column>
<el-table-column label="最后活动时间" align="center">
<template #default="scope">
{{ formatDateTime(scope.row.lastAccessedTime) }}
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['gps:equipment:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['gps:equipment:remove']"></el-button>
</el-tooltip>
<el-tooltip :content="scope.row.type === 1 ? '取消绑定' : '绑定用户'" placement="top">
<el-button
link
type="primary"
icon="User"
@click="scope.row.type === 1 ? handleUnbindUser(scope.row) : handleBindUser(scope.row)"
v-hasPermi="scope.row.type === 1 ? ['gps:equipment:unbindManmachine'] : ['gps:equipment:bindManmachine']"
>
</el-button>
</el-tooltip>
<el-tooltip content="足迹" placement="top">
<el-button
link
type="primary"
icon="Location"
v-hasPermi="['gps:equipmentSon:getList']"
@click="handleGoToEmptyPage(scope.row.userId, scope.row.projectId, scope.row.clientId)"
:disabled="!scope.row.userId || !scope.row.projectId"
></el-button>
</el-tooltip>
<el-tooltip content="历史记录" placement="top">
<el-button
link
type="primary"
icon="Clock"
@click="handleOpenHistoryUser(scope.row.clientId, scope.row.userId)"
v-hasPermi="['gps:equipment:getUserList']"
>
</el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改GPS设备详细对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="equipmentFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="设备名称" prop="deviceName">
<el-input v-model="form.deviceName" placeholder="请输入设备名称" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<!-- 绑定用户对话框 -->
<el-dialog title="绑定用户" v-model="bindDialogVisible" width="500px" append-to-body>
<el-form ref="bindUserFormRef" :model="bindForm" :rules="bindRules" label-width="80px">
<!-- 绑定用户对话框 - 项目选择下拉框 -->
<el-form-item label="项目选择" prop="projectId" required>
<el-select v-model="bindForm.projectId" placeholder="请选择项目" clearable @change="handleProjectChange">
<el-option v-for="project in projectList" :key="project.projectId" :label="project.projectName" :value="project.projectId" />
</el-select>
</el-form-item>
<el-form-item label="选择用户" prop="userId" required>
<el-select v-model="bindForm.userId" placeholder="请选择用户" clearable>
<el-option v-for="user in userList" :key="user.sysUserId" :label="user.userName" :value="user.sysUserId" />
</el-select>
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="bindButtonLoading" type="primary" @click="submitBindUser"> </el-button>
<el-button @click="cancelBindUser"> </el-button>
</div>
</template>
</el-dialog>
<!-- 历史用户弹窗 -->
<el-dialog title="设备历史用户" v-model="historyUserDialogVisible" width="600px" append-to-body @close="() => historyUserList.splice(0)">
<div v-loading="historyUserLoading" style="min-height: 200px; padding: 16px">
<el-table
:data="historyUserList"
stripe
size="small"
:show-header="historyUserList.length > 0"
v-if="!historyUserLoading || historyUserList.length > 0"
>
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="用户名" align="center" prop="userName" />
<el-table-column label="项目名称" align="center" prop="projectName" />
<el-table-column label="状态" align="center" width="120">
<template #default="scope">
<el-tag :type="scope.row.type === 0 ? 'success' : 'info'" size="small">
{{ scope.row.type === 0 ? '当前绑定' : '历史绑定' }}
</el-tag>
</template>
</el-table-column>
<el-table-column label="操作" align="center" width="80">
<template #default="scope">
<el-tooltip content="足迹" placement="top">
<el-button
link
type="primary"
icon="Location"
v-hasPermi="['gps:equipmentSon:getList']"
@click="handleGoToEmptyPage(scope.row.userId, scope.row.projectId, currentHistoryClientId)"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<div v-if="!historyUserLoading && historyUserList.length === 0" style="text-align: center; padding: 50px 0">
<el-empty description="暂无该设备的历史用户数据"></el-empty>
</div>
</div>
<template #footer>
<el-button @click="historyUserDialogVisible = false">关 闭</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup name="Equipment" lang="ts">
import {
listEquipment,
getEquipment,
delEquipment,
addEquipment,
updateEquipment,
bindUser,
getUserId,
gethistroyUser,
getRemoveBind,
getProjectId
} from '@/api/equipment/index';
import { EquipmentVO, EquipmentQuery, EquipmentForm } from '@/api/equipment/types';
import { getCurrentInstance, ComponentInternalInstance, onMounted, ref, reactive, toRefs, watch, computed } from 'vue';
import { useUserStoreHook } from '@/store/modules/user';
import { useRouter } from 'vue-router'; // 新增:导入路由钩子
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const router = useRouter(); // 新增:初始化路由实例
// 扩展EquipmentVO类型添加type字段
interface ExtendedEquipmentVO extends EquipmentVO {
type: 1 | 2; // 1=已绑定2=未绑定
}
// 设备列表相关
const equipmentList = ref<ExtendedEquipmentVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
// 表单相关引用
const queryFormRef = ref<ElFormInstance>();
const equipmentFormRef = ref<ElFormInstance>();
const bindUserFormRef = ref<ElFormInstance>();
// 项目和用户相关
const userStore = useUserStoreHook();
const currentProject = computed(() => userStore.selectedProject);
const userList = ref<any[]>([]);
const projectList = ref<any[]>([]);
const projectLoading = ref(false);
const userLoading = ref(false);
const viewAllButtonText = computed(() => {
return queryParams.value.type === 1 ? '查看未绑定项目设备' : '查看已绑定项目设备';
});
// 对话框相关
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const bindDialogVisible = ref(false);
const bindButtonLoading = ref(false);
// 历史用户弹窗相关
const historyUserDialogVisible = ref(false);
const historyUserList = ref<HistoryUserVO[]>([]);
const historyUserLoading = ref(false);
// 绑定用户表单数据
const bindForm = reactive({
id: undefined,
projectId: undefined,
userId: undefined,
deviceName: undefined
});
// 绑定用户表单验证规则
const bindRules = reactive({
projectId: [{ required: true, message: '请选择项目', trigger: 'change' }],
userId: [{ required: true, message: '请选择用户', trigger: 'change' }]
});
// 初始化表单数据
const initFormData: EquipmentForm = {
id: undefined,
projectId: undefined,
userId: undefined,
deviceName: undefined,
udp: undefined,
remoteAddressStr: undefined,
creationTime: undefined,
lastAccessedTime: undefined,
registered: undefined,
remark: undefined
};
// 页面数据
const data = reactive<PageData<EquipmentForm, EquipmentQuery>>({
form: { ...initFormData },
queryParams: {
type: 1, // 默认查询已绑定设备
pageNum: 1,
pageSize: 10,
projectId: undefined,
userId: undefined,
clientId: undefined,
deviceName: undefined,
udp: undefined,
remoteAddressStr: undefined,
creationTime: undefined,
lastAccessedTime: undefined,
registered: undefined,
params: {}
},
rules: {
deviceName: [{ required: true, message: '请输入设备名称', trigger: 'blur' }]
}
});
const { queryParams, form, rules } = toRefs(data);
/** 格式化日期时间 */
const formatDateTime = (timestamp: any): string => {
if (!timestamp) return '-';
let date: Date;
// 处理自定义的 yyyyMMddHHmmss 格式数字
if (typeof timestamp === 'number' && timestamp.toString().length === 14) {
const timeStr = timestamp.toString();
const year = timeStr.substring(0, 4);
const month = timeStr.substring(4, 6);
const day = timeStr.substring(6, 8);
const hours = timeStr.substring(8, 10);
const minutes = timeStr.substring(10, 12);
const seconds = timeStr.substring(12, 14);
date = new Date(`${year}-${month}-${day} ${hours}:${minutes}:${seconds}`);
}
// 处理时间戳(毫秒数)
else if (typeof timestamp === 'number') {
date = new Date(timestamp.toString().length === 10 ? timestamp * 1000 : timestamp);
}
// 处理字符串类型的时间戳或日期
else if (typeof timestamp === 'string') {
if (/^\d+$/.test(timestamp)) {
const num = parseInt(timestamp);
if (timestamp.length === 14) {
const year = timestamp.substring(0, 4);
const month = timestamp.substring(4, 6);
const day = timestamp.substring(6, 8);
const hours = timestamp.substring(8, 10);
const minutes = timestamp.substring(10, 12);
const seconds = timestamp.substring(12, 14);
date = new Date(`${year}-${month}-${day} ${hours}:${minutes}:${seconds}`);
} else {
date = new Date(timestamp.length === 10 ? num * 1000 : num);
}
} else {
date = new Date(timestamp);
}
}
// 其他情况
else {
return '-';
}
// 检查日期是否有效
if (isNaN(date.getTime())) return '-';
// 格式化日期
const year = date.getFullYear();
const month = String(date.getMonth() + 1).padStart(2, '0');
const day = String(date.getDate()).padStart(2, '0');
const hours = String(date.getHours()).padStart(2, '0');
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`;
};
/** 获取设备列表 */
const getList = async () => {
loading.value = true;
try {
// 已绑定设备默认关联当前项目
if (queryParams.value.type === 1 && currentProject.value?.id && !queryParams.value.projectId) {
queryParams.value.projectId = currentProject.value.id;
} else if (queryParams.value.type === 2) {
// 未绑定设备清空项目筛选
queryParams.value.projectId = undefined;
}
const res = await listEquipment(queryParams.value);
equipmentList.value = res.rows as ExtendedEquipmentVO[];
total.value = res.total;
} catch (error) {
console.error('获取设备列表失败:', error);
proxy?.$modal.msgError('获取设备列表失败,请重试');
} finally {
loading.value = false;
}
};
const getProjects = async () => {
projectLoading.value = true;
try {
const res = await getProjectId();
console.log('getProjectId接口原始返回:', res);
// 从res.data中获取数组适配外层包裹的接口格式
const rawProjects = res?.data || [];
// 验证数据类型,确保是数组
if (!Array.isArray(rawProjects)) {
console.warn('接口返回的项目数据不是数组,已自动修正为空数组');
projectList.value = [];
return;
}
// 处理项目数据使用projectId和projectName
projectList.value = rawProjects
.map((project) => ({
projectId: project.projectId,
projectName: project.projectName || '未命名项目'
}))
.filter((project) => project.projectId);
console.log('处理后项目列表:', projectList.value);
} catch (error) {
console.error('获取项目列表失败:', error);
proxy?.$modal.msgError('获取项目列表失败,请重试');
} finally {
projectLoading.value = false;
}
};
/** 根据项目ID获取用户列表 */
const getUsersByProjectId = async (projectId: any) => {
if (!projectId) {
userList.value = [];
return;
}
userLoading.value = true;
try {
const res = await getUserId(projectId);
userList.value = res.data || [];
} catch (error) {
console.error('获取用户列表失败:', error);
proxy?.$modal.msgError('获取用户列表失败,请重试');
} finally {
userLoading.value = false;
}
};
/** 项目选择变化时触发 */
const handleProjectChange = (projectId: any) => {
bindForm.userId = undefined;
getUsersByProjectId(projectId);
// 仅已绑定设备需要更新项目筛选
if (queryParams.value.type === 1) {
queryParams.value.projectId = projectId;
getList();
}
};
/** 切换查看已绑定/未绑定设备 */
const handleViewAll = () => {
queryParams.value.pageNum = 1;
if (queryParams.value.type === 1) {
// 切换到未绑定设备
queryParams.value.type = 2;
proxy?.$modal.msgSuccess('已切换到查看未绑定设备模式');
} else {
// 切换到已绑定设备
queryParams.value.type = 1;
queryParams.value.projectId = currentProject.value?.id;
proxy?.$modal.msgSuccess('已切换到查看已绑定设备模式');
}
getList();
};
const handleGoToEmptyPage = (userId: any, projectId: any, clientId: any) => {
console.log('userId:', userId, 'projectId:', projectId, 'clientId:', clientId);
router.push({
path: './equipmentGPS',
query: {
userId: userId,
projectId: projectId,
clientId: clientId
}
});
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData, projectId: currentProject.value?.id };
equipmentFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value.type = 1;
queryParams.value.projectId = currentProject.value?.id;
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: ExtendedEquipmentVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 修改按钮操作 */
const handleUpdate = async (row?: ExtendedEquipmentVO) => {
reset();
const _id = row?.id || ids.value[0];
try {
const res = await getEquipment(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改GPS设备详细';
} catch (error) {
console.error('获取设备详情失败:', error);
proxy?.$modal.msgError('获取设备详情失败,请重试');
}
};
/** 提交表单 */
const submitForm = () => {
equipmentFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
try {
if (form.value.id) {
await updateEquipment(form.value);
} else {
await addEquipment(form.value);
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
} catch (error) {
console.error('提交表单失败:', error);
proxy?.$modal.msgError('操作失败');
} finally {
buttonLoading.value = false;
}
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: ExtendedEquipmentVO) => {
const _ids = row?.id || ids.value;
try {
await proxy?.$modal.confirm('是否确认删除GPS设备详细编号为"' + _ids + '"的数据项');
await delEquipment(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
} catch (error) {
console.error('删除设备失败:', error);
proxy?.$modal.msgError('删除失败请重试');
} finally {
loading.value = false;
}
};
/** 绑定用户按钮操作 */
const handleBindUser = async (row: ExtendedEquipmentVO) => {
try {
if (!row) {
proxy?.$modal.msgWarning('未获取到设备信息');
return;
}
if (row.id === undefined || row.id === null || row.id === '') {
proxy?.$modal.msgWarning('设备ID不能为空');
return;
}
const deviceId = Number(row.id);
if (isNaN(deviceId) || deviceId <= 0) {
proxy?.$modal.msgWarning(`设备ID格式错误必须是正整数当前值: ${row.id}`);
return;
}
if (!row.clientId || typeof row.clientId !== 'string' || row.clientId.trim() === '') {
proxy?.$modal.msgWarning(`设备标识clientId格式错误必须是非空字符串当前值: ${row.clientId}`);
return;
}
// 初始化表单
Object.assign(bindForm, {
id: deviceId,
projectId: undefined,
userId: undefined,
clientId: row.clientId,
deviceName: row.deviceName || '未知设备'
});
bindUserFormRef.value?.resetFields();
userList.value = [];
// 加载项目和用户数据
if (projectList.value.length === 0) {
await getProjects();
}
if (currentProject.value?.id) {
bindForm.projectId = currentProject.value.id;
await getUsersByProjectId(currentProject.value.id);
}
bindDialogVisible.value = true;
} catch (error) {
console.error('打开绑定用户对话框失败:', error);
proxy?.$modal.msgError('操作失败请重试');
}
};
/** 提交绑定用户 */
const submitBindUser = () => {
bindUserFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
if (!bindForm.clientId) {
proxy?.$modal.msgWarning('设备标识clientId不存在无法完成绑定');
bindButtonLoading.value = false;
return;
}
bindButtonLoading.value = true;
try {
// 直接使用bindForm数据调用bindUser接口
console.log('提交绑定用户参数:', bindForm);
await bindUser(bindForm);
proxy?.$modal.msgSuccess('用户绑定成功');
bindDialogVisible.value = false;
await getList();
} catch (error) {
console.error('绑定用户失败:', error);
proxy?.$modal.msgError('绑定失败请重试');
} finally {
bindButtonLoading.value = false;
}
}
});
};
/** 取消绑定用户对话框 */
const cancelBindUser = () => {
bindDialogVisible.value = false;
};
/** 取消用户绑定操作 */
const handleUnbindUser = async (row: ExtendedEquipmentVO) => {
try {
if (!row) {
proxy?.$modal.msgWarning('未获取到设备信息');
return;
}
if (row.id === undefined || row.id === null || row.id === '') {
proxy?.$modal.msgWarning('设备ID不能为空');
return;
}
const deviceId = Number(row.id);
if (isNaN(deviceId) || deviceId <= 0) {
proxy?.$modal.msgWarning(`设备ID格式错误必须是正整数当前值: ${row.id}`);
return;
}
if (!row.clientId || typeof row.clientId !== 'string' || row.clientId.trim() === '') {
proxy?.$modal.msgWarning(`设备标识clientId格式错误必须是非空字符串当前值: ${row.clientId}`);
return;
}
const clientId = row.clientId.trim();
const unbindParams = {
id: deviceId,
clientId: clientId
};
await proxy?.$modal.confirm('是否确认解除该设备的用户绑定');
const response = await getRemoveBind(unbindParams);
if (response && response.code === 200) {
proxy?.$modal.msgSuccess('解除绑定成功');
await getList();
} else {
throw new Error(`接口返回非成功状态: ${JSON.stringify(response)}`);
}
} catch (error) {
console.error('解除绑定 - 错误详情:', error);
if (error === 'cancel' || (error instanceof Error && error.message.includes('cancel'))) {
console.log('用户取消了解除绑定操作');
return;
}
const errorMsg = error instanceof Error ? error.message : typeof error === 'string' ? error : '未知错误';
proxy?.$modal.msgError(`解除绑定失败: ${errorMsg}`);
}
};
/** 打开历史用户弹窗 */
const handleOpenHistoryUser = async (clientId: string | number | undefined, currentUserId: string | number | undefined) => {
if (!clientId) {
proxy?.$modal.msgWarning('设备标识clientId不存在无法获取历史用户');
return;
}
historyUserDialogVisible.value = true;
historyUserLoading.value = true;
historyUserList.value = [];
// 保存当前clientId用于足迹操作
currentHistoryClientId.value = clientId;
try {
const res = await gethistroyUser({
clientId: clientId,
userId: currentUserId || ''
});
const rawUserList = res.data || [];
if (currentUserId && rawUserList.length > 0) {
const currentUser = rawUserList.find((user: HistoryUserVO) => user.sysUserId === currentUserId);
const historyUsers = rawUserList.filter((user: HistoryUserVO) => user.sysUserId !== currentUserId);
historyUserList.value = currentUser ? [currentUser, ...historyUsers] : historyUsers;
} else {
historyUserList.value = rawUserList;
}
} catch (error) {
console.error('获取历史用户接口调用失败clientId%s:', clientId, error);
proxy?.$modal.msgError('获取历史用户失败请重试');
historyUserList.value = [];
} finally {
historyUserLoading.value = false;
}
};
/** 历史弹窗中的足迹操作 */
const handleFootprintOperation = () => {
if (!historyUserList.value || historyUserList.value.length === 0) {
proxy?.$modal.msgWarning('暂无用户数据无法查看足迹');
return;
}
// 获取第一个用户的数据(通常是当前绑定用户)
const firstUser = historyUserList.value[0];
if (!firstUser.sysUserId) {
proxy?.$modal.msgWarning('用户ID不存在无法查看足迹');
return;
}
// 跳转到足迹页面
handleGoToEmptyPage(firstUser.sysUserId, firstUser.projectId || currentProject.value?.id, currentHistoryClientId.value);
};
// 当前历史弹窗的clientId
const currentHistoryClientId = ref('');
/** 页面挂载时初始化 */
onMounted(() => {
getList();
});
/** 监听项目变化 */
const listeningProject = watch(
() => currentProject.value?.id,
(nid) => {
if (queryParams.value.type === 1) {
queryParams.value.projectId = nid;
form.value.projectId = nid;
getList();
}
}
);
/** 页面卸载时清理 */
onUnmounted(() => {
listeningProject();
});
// 类型定义
interface DialogOption {
visible: boolean;
title: string;
}
interface PageData<T, Q> {
form: T;
queryParams: Q;
rules: Record<string, any[]>;
}
interface HistoryUserVO {
sysUserId: string | number;
userName: string;
lastUseTime?: number | string;
bindTime?: number | string;
type?: number; // 1:当前绑定,其他:历史绑定
}
</script>

View File

@ -1,6 +1,7 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter"
:leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="100px">
@ -11,25 +12,15 @@
<el-input v-model="queryParams.formalitiesId" placeholder="请输入手续办理清单模板id" clearable @keyup.enter="handleQuery" />
</el-form-item> -->
<el-form-item label="计划开始时间" prop="planTheStartTime">
<el-date-picker
clearable
v-model="queryParams.planTheStartTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择计划开始时间"
/>
<el-date-picker clearable v-model="queryParams.planTheStartTime" type="date" value-format="YYYY-MM-DD"
placeholder="请选择计划开始时间" />
</el-form-item>
<el-form-item label="负责人" prop="head">
<el-input v-model="queryParams.head" placeholder="请输入负责人" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="实际完成时间" prop="actualCompletionTime">
<el-date-picker
clearable
v-model="queryParams.actualCompletionTime"
type="date"
value-format="YYYY-MM-DD"
placeholder="请选择实际完成时间"
/>
<el-date-picker clearable v-model="queryParams.actualCompletionTime" type="date" value-format="YYYY-MM-DD"
placeholder="请选择实际完成时间" />
</el-form-item>
<!-- <el-form-item label="手续材料" prop="formalitiesUrl">
<el-input v-model="queryParams.formalitiesUrl" placeholder="请输入手续材料" clearable @keyup.enter="handleQuery" />
@ -47,23 +38,25 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd()" v-hasPermi="['formalities:formalitiesAreConsolidated:getTree']"
>新增</el-button
>
<span style="margin-left: 10px"
><el-tooltip class="box-item" effect="dark" content="从原有模板列表选择新增" placement="top">
<el-icon color="#409efc"><WarningFilled /></el-icon> </el-tooltip
></span>
<el-button type="primary" plain icon="Plus" @click="handleAdd()"
v-hasPermi="['formalities:formalitiesAreConsolidated:getTree']">新增</el-button>
<span style="margin-left: 10px"><el-tooltip class="box-item" effect="dark" content="从原有模板列表选择新增"
placement="top">
<el-icon color="#409efc">
<WarningFilled />
</el-icon> </el-tooltip></span>
</el-col>
<el-col :span="1.5" v-hasPermi="['formalities:listOfFormalities:list']">
<el-button type="primary" plain icon="Plus" @click="addTemplate()">新增数据</el-button>
<span style="margin-left: 10px">
<el-tooltip class="box-item" effect="dark" content="创建新模板并添加数据" placement="top">
<el-icon color="#409efc"><WarningFilled /></el-icon>
<el-icon color="#409efc">
<WarningFilled />
</el-icon>
</el-tooltip>
</span>
</el-col>
<el-col :span="1.5">
<!-- <el-col :span="1.5">
<el-button
type="success"
plain
@ -73,15 +66,16 @@
v-hasPermi="['formalities:formalitiesAreConsolidated:edit']"
>修改</el-button
>
</el-col>
</el-col> -->
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="formalitiesAreConsolidatedList" @selection-change="handleSelectionChange" row-key="id" default-expand-all>
<el-table v-loading="loading" :data="formalitiesAreConsolidatedList" @selection-change="handleSelectionChange"
row-key="id" default-expand-all>
<el-table-column type="selection" width="55" align="center" />
<!-- <el-table-column label="手续办理清单模板父级" align="center" prop="formalitiesPname" /> -->
<el-table-column label="手续办理清单" align="center" prop="formalitiesName" />
<el-table-column label="手续办理清单" align="left" prop="formalitiesName" />
<el-table-column label="计划开始时间" align="center" prop="planTheStartTime" width="180">
<template #default="scope">
<span>{{ parseTime(scope.row.planTheStartTime, '{y}-{m}-{d}') }}</span>
@ -101,51 +95,52 @@
<el-table-column label="办理状态" align="center" prop="processingStatus" />
<el-table-column label="手续材料" align="center" prop="formalitiesUrl" width="180">
<template #default="scope">
<el-link type="primary" :underline="false" @click="handlePreview(scope.row)" target="_blank" v-if="scope.row.formalitiesPid"
>查看</el-link
>
<div style="display: flex; justify-content: center; align-items: center;">
<div>
<el-link type="primary" :underline="false" @click="handlePreview(scope.row)" target="_blank"
v-if="scope.row.formalitiesPid">查看</el-link>
</div>
<div>
<el-badge v-if="scope.row.fileCount" :value="scope.row.fileCount" class="item" type="danger">
</el-badge>
</div>
</div>
</template>
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<div v-if="scope.row.formalitiesPid">
<el-button
link
type="primary"
icon="Edit"
v-if="scope.row.processingStatus != '已完成'"
<el-button link type="primary" icon="Edit" v-if="scope.row.processingStatus != '已完成'"
@click="handleUpdate(scope.row)"
v-hasPermi="['formalities:formalitiesAreConsolidated:edit']"
>修改</el-button
>
<el-button link type="primary" icon="Upload" v-if="scope.row.processingStatus != '已完成'" @click="handleUpload(scope.row)"
>上传</el-button
>
<el-button
link
type="primary"
icon="EditPen"
@click="handleUpdateStatus(scope.row)"
v-hasPermi="['formalities:formalitiesAreConsolidated:edit']"
>修改状态</el-button
>
v-hasPermi="['formalities:formalitiesAreConsolidated:edit']">修改</el-button>
<el-button link type="primary" icon="Upload" v-if="scope.row.processingStatus != '已完成'"
@click="handleUpload(scope.row)">上传</el-button>
<el-button link type="primary" icon="EditPen" @click="handleUpdateStatus(scope.row)"
v-hasPermi="['formalities:formalitiesAreConsolidated:edit']">修改状态</el-button>
</div>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改合规性手续合账对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="formalitiesAreConsolidatedFormRef" :model="form" :rules="rules" label-width="160px">
<el-form-item label="计划开始时间" prop="planTheStartTime">
<el-date-picker clearable v-model="form.planTheStartTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择计划开始时间">
<el-date-picker clearable v-model="form.planTheStartTime" type="date" value-format="YYYY-MM-DD"
placeholder="请选择计划开始时间">
</el-date-picker>
</el-form-item>
<el-form-item label="计划完成时间" prop="planTheStartTime">
<el-date-picker clearable v-model="form.planTheEndTime" type="date" value-format="YYYY-MM-DD" placeholder="请选择计划完成时间">
<el-date-picker clearable v-model="form.planTheEndTime" type="date" value-format="YYYY-MM-DD"
placeholder="请选择计划完成时间">
</el-date-picker>
</el-form-item>
<el-form-item label="负责人" prop="head">
@ -167,24 +162,20 @@
<el-table :data="fileList" style="width: 100%" border v-loading="fileLoading">
<el-table-column prop="fileName" label="文件" align="center">
<template #default="scope">
<el-link :key="scope.row.annexUrl" :href="scope.row.annexUrl" target="_blank" type="primary" :underline="false">
<el-link :key="scope.row.annexUrl" :href="scope.row.annexUrl" target="_blank" type="primary"
:underline="false">
{{ scope.row.fileName || '查看文件' }}
</el-link>
</template>
</el-table-column>
<el-table-column label="操作" width="90" align="center" v-if="fileStatus != '1'">
<el-table-column label="操作" align="center" v-if="fileStatus != '1'">
<template #default="scope">
<el-button type="danger" link icon="Delete" @click="handleDeleteFile(scope.row)"> 删除 </el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="fileTotal > 0"
:total="fileTotal"
v-model:page="fileParams.pageNum"
v-model:limit="fileParams.pageSize"
@pagination="getFileList"
/>
<pagination v-show="fileTotal > 0" :total="fileTotal" v-model:page="fileParams.pageNum"
v-model:limit="fileParams.pageSize" @pagination="getFileList" />
<template #footer>
<span>
<el-button type="primary" @click="viewVisible = false">关闭</el-button>
@ -194,18 +185,9 @@
<!-- 上传文件对话框 -->
<el-dialog draggable title="上传文件" v-model="fileVisible" width="450">
<el-form-item label="上传文件" prop="processingStatus">
<file-upload
v-model="file"
ref="uploadRef"
uploadUrl="/formalities/formalitiesAnnex"
v-hasPermi="['formalities:formalitiesAnnex:add']"
:data="{ formalitiesId: form.id }"
:fileType="['pdf']"
:auto-upload="false"
showFileList
method="put"
:onUploadSuccess="handleUploadSuccess"
/>
<file-upload v-model="file" ref="uploadRef" uploadUrl="/formalities/formalitiesAnnex"
v-hasPermi="['formalities:formalitiesAnnex:add']" :data="{ formalitiesId: form.id }" :fileType="['pdf']"
:auto-upload="false" showFileList method="put" :onUploadSuccess="handleUploadSuccess" />
</el-form-item>
<template #footer>
<span>
@ -233,21 +215,19 @@
</el-dialog>
<el-dialog title="新增合规性手续合账" v-model="templateVisbile" width="450">
<el-form-item label="合规性手续模板">
<el-cascader
v-model="tempValue"
:options="tempTreeList"
:props="{
multiple: true,
value: 'id',
label: 'name',
disabled: (node: any) => {
return (node.pid == 0 && !node.children.length) || node.status == 1; // 有 parent 的是二级,没有 parent 的是一级,禁用一级
}
}"
/>
<el-cascader v-model="tempValue" :options="tempTreeList" :props="{
multiple: true,
value: 'id',
label: 'name',
disabled: (node: any) => {
return (node.pid == 0 && !node.children.length) || node.status == 1; // 有 parent 的是二级,没有 parent 的是一级,禁用一级
}
}" />
<div style="margin-left: 10px; display: flex; justify-content: center; align-items: center">
<el-tooltip class="box-item" effect="dark" content="列表上已选择得模版不可再选" placement="top">
<el-icon><WarningFilled /></el-icon>
<el-icon>
<WarningFilled />
</el-icon>
</el-tooltip>
</div>
</el-form-item>
@ -268,7 +248,8 @@
<el-option v-for="item in listOfFormalitiesList" :label="item.name" :value="item.id" />
</el-select>
</el-form-item>
<el-form-item label="名称" prop="formalitiesName" :rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]">
<el-form-item label="名称" prop="formalitiesName"
:rules="[{ required: true, message: '请输入名称', trigger: 'blur' }]">
<el-input v-model="formTemplate.formalitiesName" placeholder="请输入名称" />
</el-form-item>
</el-form>

View File

@ -31,13 +31,20 @@
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="listOfFormalitiesList" @selection-change="handleSelectionChange" row-key="id" default-expand-all>
<el-table
v-loading="loading"
:data="listOfFormalitiesList"
@selection-change="handleSelectionChange"
row-key="id"
default-expand-all
style="height: calc(100vh - 300px); overflow-y: auto"
>
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="名称" prop="name" />
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改手续办理清单模板对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="listOfFormalitiesFormRef" :model="form" :rules="rules" label-width="80px">

View File

@ -0,0 +1,90 @@
<template>
<div class="p-2">
<el-table size="small" v-if="props.row.length !== 0" :data="props.row">
<el-table-column label="材料名称" align="center" prop="materialsName" />
<!-- <el-table-column label="材料数量" align="center" prop="quantityCount" /> -->
<el-table-column label="剩余量" align="center" prop="residue">
<template #default="scope">
<span>{{ scope.row.residue }}</span>
</template>
</el-table-column>
<el-table-column label="出库数量" align="center" prop="number">
<template #default="scope">
<el-input-number v-if="scope.row.type" v-model="scope.row.number" :controls="false" :min="0" :max="scope.row.residue" :precision="0" />
<span v-else>{{ scope.row.number }}</span>
</template>
</el-table-column>
<el-table-column label="交接单位" align="center" prop="recipient">
<template #default="scope">
<el-input v-if="scope.row.type" style="width: 150px" v-model="scope.row.recipient" />
<span v-else>{{ scope.row.recipient }}</span>
</template>
</el-table-column>
<el-table-column label="领用人" align="center" prop=" shipper">
<template #default="scope">
<el-input v-if="scope.row.type" style="width: 150px" v-model="scope.row.shipper" />
<span v-else>{{ scope.row.shipper }}</span>
</template>
</el-table-column>
<el-table-column label="操作人" align="center" prop="operator">
<template #default="scope">
<el-input v-if="scope.row.type" style="width: 150px" v-model="scope.row.operator" />
<span v-else>{{ scope.row.operator }}</span>
</template>
</el-table-column>
<el-table-column label="出库时间" align="center" prop="outPutTime" width="180" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" @click="handleConfirm(scope.row)" v-if="scope.row.type"> 确认 </el-button>
</template>
</el-table-column>
</el-table>
</div>
</template>
<script setup lang="ts">
import { useUserStoreHook } from '@/store/modules/user';
import { addOutbound } from '@/api/materials/materialOutbound';
// 获取用户 store
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const emit = defineEmits(['success']);
const props = defineProps({
row: {
type: Array,
default: () => []
}
});
const handleConfirm = async (row: any) => {
if (row.number == 0 || !row.number) {
ElMessage.error('请输入出库数量');
return;
}
if (!row.shipper) {
ElMessage.error('请输入领用人');
return;
}
if (!row.operator) {
ElMessage.error('请输入操作人');
return;
}
console.log(row);
const params = {
...row,
materialsId: row.id,
projectId: currentProject.value?.id,
outPut: '1'
};
console.log(params);
const data = await addOutbound(params);
if (data.code === 200) {
ElMessage.success('出库成功');
emit('success');
}
};
</script>

View File

@ -0,0 +1,148 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="材料名称" prop="materialName">
<el-input v-model="queryParams.materialName" placeholder="请输入材料名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<el-table
v-loading="loading"
:data="tableList"
:row-key="
(row) => {
return row.id;
}
"
@row-click="
(row, column, event) => {
// 阻止点击行时自动展开
if (column.property) event.stopPropagation();
}
"
:preserve-expanded-content="true"
>
<el-table-column type="expand">
<template #default="{ row }">
<outbound :row="row.children ?? []" @success="getTableList" />
</template>
</el-table-column>
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="材料名称" align="center" prop="materialsName" />
<el-table-column label="规格" align="center" prop="typeSpecificationName" />
<el-table-column label="计量单位" align="center" prop="weightId" />
<el-table-column label="材料数量" align="center" prop="quantityCount" />
<el-table-column label="剩余量" align="center" prop="residue" />
<el-table-column label="操作人" align="center" prop="operator"> </el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Plus" @click="handleoutbound(scope.row)"> 新增出库 </el-button>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getTableList"
/>
</el-card>
</div>
</template>
<script setup lang="ts">
import { outboundMaterials } from '@/api/materials/materialOutbound';
import { useUserStoreHook } from '@/store/modules/user';
import outbound from './component/outbound.vue';
import { number } from 'vue-types';
const { proxy } = getCurrentInstance() as any;
const queryFormRef = ref();
const queryParams = ref({
materialName: '',
pageNum: 1,
pageSize: 10
});
const total = ref(0);
const loading = ref(false);
const tableList = ref([]);
// 获取用户 store
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
//获取列表数据
const getTableList = async () => {
try {
loading.value = true;
const res = await outboundMaterials({ ...queryParams.value, projectId: currentProject.value?.id });
if (res.code === 200) {
loading.value = false;
tableList.value = res.rows;
total.value = res.data.total;
}
} catch (error) {
loading.value = false;
}
};
getTableList();
// 出入库
const handleoutbound = (row: any) => {
console.log(row);
if (row.children == null) {
row.children = [];
}
if (row.children.some((child) => child.type === 'add')) {
ElMessage.warning('已经存在出库记录,不能重复添加');
return;
}
row.children.push({
type: 'add',
id: row.id,
number: 0,
operator: '',
shipper: '',
recipient: '',
residue: row.residue,
materialsName: row.materialsName
});
// 手动触发展开行
const table = document.querySelector('.el-table__body-wrapper');
const rows = table?.querySelectorAll('.el-table__row');
const rowEl = rows?.[Array.from(tableList.value).indexOf(row)];
const expandBtn: any = rowEl?.querySelector('.el-table__expand-icon');
// 如果行未展开,则点击展开按钮
if (expandBtn && !expandBtn.classList.contains('el-table__expand-icon--expanded')) {
expandBtn.click();
}
};
//搜索
const handleQuery = () => {
queryParams.value.pageNum = 1;
getTableList();
};
//重置
const resetQuery = () => {
console.log(111111111);
queryFormRef.value?.resetFields();
handleQuery();
};
</script>
<style scoped></style>

View File

@ -27,19 +27,6 @@
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['materials:materials:add']"> 新增 </el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['materials:materials:edit']"
>修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['materials:materials:remove']"
>删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['materials:materials:export']">导出 </el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
@ -72,18 +59,22 @@
<!-- <el-table-column fixed="right" label="操作" align="center" class-name="small-padding fixed-width" width="320">
<template #default="scope">
<el-space>
<el-button link type="primary" icon="View" @click="handleShowDrawer(scope.row)"
v-hasPermi="['materials:materials:query']">
<el-button link type="primary" icon="View" @click="handleShowDrawer(scope.row)" v-hasPermi="['materials:materials:query']">
详情
</el-button>
<el-button link type="success" icon="Edit" @click="handleUpdate(scope.row)"
v-hasPermi="['materials:materials:edit']"> 修改 </el-button>
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)"
v-hasPermi="['materials:materials:remove']">
<el-button link type="success" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['materials:materials:edit']"> 修改 </el-button>
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['materials:materials:remove']">
删除
</el-button>
<el-button v-hasPermi="['materials:materialsInventory:edit']" link type="primary" icon="Plus"
@click="handleAddMaterialsInventory(scope.row)"> 出入库 </el-button>
<el-button
v-hasPermi="['materials:materialsInventory:edit']"
link
type="primary"
icon="Plus"
@click="handleAddMaterialsInventory(scope.row)"
>
出入库
</el-button>
</el-space>
</template>
</el-table-column> -->
@ -92,48 +83,57 @@
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改材料名称对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-dialog draggable :title="dialog.title" v-model="dialog.visible" width="800px" append-to-body>
<el-form ref="materialsFormRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="材料名称" prop="materialsName">
<el-input v-model="form.materialsName" placeholder="请输入材料名称" />
</el-form-item>
<el-form-item label="规格型号名称" prop="typeSpecificationName">
<el-input v-model="form.typeSpecificationName" placeholder="请输入规格型号名称" />
</el-form-item>
<el-form-item label="材料供应商" prop="companyId">
<el-select v-model="form.companyId" clearable placeholder="请选择材料提供商">
<el-option v-for="item in companyOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="使用部位" prop="usePart">
<el-input v-model="form.usePart" placeholder="请输入使用部位" />
</el-form-item>
<el-form-item label="计量单位" prop="weightId">
<el-input v-model="form.weightId" placeholder="请输入计量单位" />
</el-form-item>
<el-form-item label="预计材料数量" prop="quantityCount">
<el-input v-model="form.quantityCount" placeholder="请输入预计材料数量" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
<el-form-item label="材料文件" prop="fileOssIdMap">
<div :key="item.value" v-for="item in materials_file_type">
<h3>{{ item.label }}</h3>
<file-upload
v-model="ossIdMap[item.value]"
:limit="1"
:file-size="50"
:file-type="['pdf']"
@update:model-value="
(args) => {
handleOssUpdate(args, item.value);
}
"
/>
</div>
</el-form-item>
<el-row>
<el-col :span="12">
<el-form-item label="材料名称" prop="materialsName"> <el-input v-model="form.materialsName" placeholder="请输入材料名称" /> </el-form-item
></el-col>
<el-col :span="12">
<el-form-item label="规格型号名称" prop="typeSpecificationName">
<el-input v-model="form.typeSpecificationName" placeholder="请输入规格型号名称" /> </el-form-item
></el-col>
<el-col :span="12"
><el-form-item label="材料提供商" prop="companyId">
<el-select v-model="form.companyId" clearable placeholder="请选择材料提供商">
<el-option v-for="item in companyOptions" :key="item.value" :label="item.label" :value="item.value" />
</el-select> </el-form-item
></el-col>
<el-col :span="12">
<el-form-item label="使用部位" prop="usePart"> <el-input v-model="form.usePart" placeholder="请输入使用部位" /> </el-form-item
></el-col>
<el-col :span="12"
><el-form-item label="计量单位" prop="weightId"> <el-input v-model="form.weightId" placeholder="请输入计量单位" /> </el-form-item
></el-col>
<el-col :span="12">
<el-form-item label="预计材料数量" prop="quantityCount">
<el-input v-model="form.quantityCount" type="number" min="0" placeholder="请输入预计材料数量" /> </el-form-item
></el-col>
<el-col :span="24">
<el-form-item label="备注" prop="remark"> <el-input type="textarea" v-model="form.remark" placeholder="请输入备注" /> </el-form-item
></el-col>
<el-col :span="12" :key="item.value" v-for="item in materials_file_type">
<el-form-item label="材料文件" prop="fileOssIdMap">
<div>
<h3>{{ item.label }}</h3>
<file-upload
:isShowTip="false"
v-model="ossIdMap[item.value]"
:limit="1"
:file-size="50"
:file-type="['pdf']"
@update:model-value="
(args) => {
handleOssUpdate(args, item.value);
}
"
/>
</div> </el-form-item
></el-col>
<el-col :span="24" style="color: rgb(237 70 61)">注意:请上传pdf格式文件</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm">确 定</el-button>
@ -154,7 +154,7 @@ import { MaterialsForm, MaterialsQuery, MaterialsVO } from '@/api/materials/mate
import { useUserStoreHook } from '@/store/modules/user';
import MaterialsInventoryTable from '@/views/materials/materials/component/MaterialsInventoryTable.vue';
import MaterialsInventoryAddDialog from '@/views/materials/materials/component/MaterialsInventoryAddDialog.vue';
import { listCompany } from '@/api/materials/company';
import { listCompany, supplierInputGet } from '@/api/materials/company';
import { CompanyVO } from '@/api/materials/company/types';
import MaterialsDetailDrawer from '@/views/materials/materials/component/MaterialsDetailDrawer.vue';
@ -230,15 +230,14 @@ const getList = async () => {
/** 获取当前项目下的公司列表 */
const getCompanyList = async () => {
loading.value = true;
const companyRes = await listCompany({
pageNum: 1,
pageSize: 1000,
const companyRes = await supplierInputGet({
projectId: currentProject.value?.id
});
companyOptions.value = companyRes.data.list.map((company: CompanyVO) => ({
value: company.companyId + '',
label: company.companyName,
elTagType: 'default'
console.log(companyRes);
companyOptions.value = companyRes.data.map((company) => ({
value: company.id,
label: company.supplierName
}));
console.log('🚀 ~ getCompanyList ~ companyOptions.value:', companyOptions.value);
loading.value = false;
@ -368,6 +367,7 @@ const listeningProject = watch(
(nid, oid) => {
queryParams.value.projectId = nid;
form.value.projectId = nid;
getCompanyList();
getList();
}
);

View File

@ -73,7 +73,10 @@
<el-row>
<el-col :span="12">
<el-form-item label="表单编号" prop="formCode">
<el-input v-model="form.formCode" placeholder="请输入表单编号" />
<!-- <el-input v-model="form.formCode" placeholder="请输入表单编号" /> -->
<el-select v-model="form.formCode" placeholder="请选择表单编号" @change="(value) => formCodeChange(value)">
<el-option v-for="item in options" :key="item.formCode" :label="item.formCode" :value="item.formCode" />
</el-select>
</el-form-item>
</el-col>
<el-col :span="12">
@ -123,11 +126,11 @@
<el-col :span="12">
<el-form-item
label="名称"
:prop="`itemList.${index}.name`"
:prop="`itemList.${index}.materialsId`"
:rules="[{ required: true, message: '名称不能为空', trigger: 'blur' }]"
>
<el-select v-model="item.inventoryId" placeholder="请选择名称" @change="(value) => getNameChange(value, index, item)">
<el-option v-for="opt in optionsName" :key="opt.id" :label="opt.materialsName" :value="opt.id" />
<el-select v-model="item.materialsId" placeholder="请选择名称" @change="(value) => getNameChange(value, index, item)">
<el-option v-for="opt in optionsName" :key="opt.id" :label="`${opt.materialsName}_${opt.createTime}`" :value="opt.id" />
</el-select>
</el-form-item>
</el-col>
@ -168,13 +171,9 @@
</el-col> -->
<el-col :span="12">
<el-form-item label="领取" :prop="`itemList.${index}.issuedQuantity`">
<el-input
v-model.number="item.issuedQuantity"
disabled
placeholder="请输入领取数量"
@input="handleIssuedChange(index)"
@blur="handleIssuedChange(index)"
/>
<el-select v-model="item.issuedQuantity" placeholder="请选择数量">
<el-option v-for="opt in item.outList" :key="opt.id" :label="opt.number" :value="opt.number" />
</el-select>
</el-form-item>
</el-col>
<!-- <el-col :span="12">
@ -247,7 +246,8 @@ import {
addMaterialIssue,
updateMaterialIssue,
inventoryList,
getMaterialName
getMaterialName,
getMaterialInfo
} from '@/api/materials/materialIssue';
import { MaterialIssueVO, MaterialIssueQuery, MaterialIssueForm } from '@/api/materials/materialIssue/types';
@ -307,6 +307,7 @@ const getInitFormData = () => {
itemList: [
{
id: undefined,
specification: undefined,
unit: undefined,
stockQuantity: undefined,
@ -314,7 +315,8 @@ const getInitFormData = () => {
remainingQuantity: undefined,
name: undefined, // 数量验收的名称
remark: undefined,
materialsId: undefined
materialsId: undefined,
outList: []
}
]
};
@ -365,7 +367,7 @@ const computeMaterialName = () => {
.map((item) => item.name.trim())
.filter((name, index, self) => self.indexOf(name) === index); // 去重(如需保留重复则删除这行)
form.value.materialName = validNames.join(',');
// form.value.materialName = validNames.join(',');
};
/** 查询物料领料单列表 */
@ -404,6 +406,7 @@ const getNameChange = (value, index, item) => {
item.unit = selected.weightId;
item.issuedQuantity = selected.number;
item.stockQuantity = Number(selected.inventoryNumber) || 0;
item.outList = selected.outList || [];
// calculateRemaining(index); // 计算剩余数量
}
};
@ -529,8 +532,27 @@ const handleAdd = () => {
dialog.title = '添加物料领料单';
// 新增时初始计算材料名称
computeMaterialName();
getFormData();
};
const options = ref([]);
//新增获取表单数据
const getFormData = async () => {
const res = await getMaterialInfo(currentProject.value.id);
if (res.code == 200) {
options.value = res.data;
}
};
const formCodeChange = (value) => {
console.log(value);
const selected = options.value.find((opt) => opt.formCode === value);
if (selected) {
form.value.materialName = selected.materialName;
form.value.orderingUnit = selected.orderingUnit;
form.value.supplierUnit = selected.supplierUnit;
optionsName.value = selected.materials;
}
};
/** 修改按钮操作 */
const handleUpdate = async (row?: MaterialIssueVO) => {
reset();
@ -597,6 +619,7 @@ const submitForm = () => {
remainingQuantity: Number(item.remainingQuantity)
}))
};
console.log('提交数据:', submitData);
if (form.value.id) {
await updateMaterialIssue(submitData);
@ -638,10 +661,13 @@ const addItem = () => {
// 删除数量验收条目
const removeItem = (index: number) => {
if (form.value.itemList.length > 1) {
console.log(111111);
console.log(itemWatchStopFns.value[index]);
// 停止该条目的监听
if (itemWatchStopFns.value[index]) {
itemWatchStopFns.value[index]();
}
// if (itemWatchStopFns.value[index]) {
// itemWatchStopFns.value[index]();
// }
form.value.itemList.splice(index, 1);
itemWatchStopFns.value.splice(index, 1);
// 删除后重新计算材料名称
@ -682,7 +708,7 @@ watch(
onMounted(() => {
getList();
getName();
// getName();
});
// 监听项目id刷新数据
@ -692,7 +718,7 @@ const listeningProject = watch(
queryParams.value.projectId = nid;
form.value.projectId = nid;
getList();
getName();
// getName();
}
);

View File

@ -42,7 +42,7 @@
<thead>
<tr>
<th colspan="2">领料单位</th>
<td class="th-bg" colspan="2">{{ formData.placeholder }}</td>
<td class="th-bg" colspan="2">{{ formData.issueUnit }}</td>
<th colspan="2">保管单位</th>
<td class="th-bg" colspan="2">{{ formData.storageUnit }}</td>
</tr>

View File

@ -1,14 +1,13 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter"
:leave-active-class="proxy?.animate.searchAnimate.leave">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="表单编号" prop="formCode">
<el-input v-model="queryParams.formCode" placeholder="请输入表单编号" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="材料来源" prop="materialSource">
<el-form-item label="材料来源" prop="materialSource">
<el-select v-model="queryParams.materialSource" filterable placeholder="请选择材料来源">
<el-option label="全部" value="0"></el-option>
<el-option label="甲供材料" value="1"></el-option>
@ -16,8 +15,7 @@
</el-select>
</el-form-item>
<el-form-item label="材料名称" prop="materialName">
<el-input v-model="queryParams.materialName" placeholder="请输入设备材料名称" clearable
@keyup.enter="handleQuery" />
<el-input v-model="queryParams.materialName" placeholder="请输入设备材料名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="订货单位" prop="orderingUnit">
<el-input v-model="queryParams.orderingUnit" placeholder="请输入订货单位" clearable @keyup.enter="handleQuery" />
@ -38,8 +36,7 @@
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd"
v-hasPermi="['materials:materialReceive:add']">新增</el-button>
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['materials:materialReceive:add']">新增</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -50,8 +47,9 @@
<el-table-column label="表单编号" align="center" prop="formCode" />
<el-table-column label="材料来源" align="center" prop="projectName">
<template #default="scope">
<el-tag :type="scope.row.materialSource == '1' ? 'success' : 'warning'">{{ scope.row.materialSource == '1' ?
'甲供材料' : '乙供材料' }}</el-tag>
<el-tag :type="scope.row.materialSource == '1' ? 'success' : 'warning'">{{
scope.row.materialSource == '1' ? '甲供材料' : '乙供材料'
}}</el-tag>
</template>
</el-table-column>
<el-table-column label="设备材料名称" align="center" prop="materialName" />
@ -77,20 +75,28 @@
<!-- <el-button link type="primary" icon="edit" @click="handleUpdate(scope.row)" v-hasPermi="['materials:materialReceive:edit']"
>修改</el-button
> -->
<el-button link type="primary" icon="View" @click="handleView(scope.row)"
v-hasPermi="['materials:materialReceive:query']">查看</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)"
v-hasPermi="['materials:materialReceive:remove']">删除</el-button>
<el-button link type="primary" icon="View" @click="handleView(scope.row)" v-hasPermi="['materials:materialReceive:query']"
>查看</el-button
>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['materials:materialReceive:remove']"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="getList" />
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改物料接收单对话框 -->
<el-dialog :close-on-click-modal="false" :close-on-press-escape="false" draggable :title="dialog.title"
v-model="dialog.visible" width="800px" append-to-body>
<el-dialog
:close-on-click-modal="false"
:close-on-press-escape="false"
draggable
:title="dialog.title"
v-model="dialog.visible"
width="800px"
append-to-body
>
<el-form ref="materialReceiveFormRef" :model="form" :rules="rules" label-width="110px">
<el-row>
<el-col :span="12">
@ -108,10 +114,8 @@
</el-col>
<el-col v-if="form.materialSource == '2'" :span="12">
<el-form-item label="采购单编号" prop="docId">
<el-select @change="handleSelect" v-model="form.docId" filterable placeholder="请选择采购单"
style="width: 100%">
<el-option v-for="item in purchaseDocList" :key="item.id" :label="item.docCode"
:value="item.id"></el-option>
<el-select @change="handleSelect" v-model="form.docId" filterable placeholder="请选择采购单" style="width: 100%">
<el-option v-for="item in purchaseDocList" :key="item.id" :label="item.docCode" :value="item.id"></el-option>
</el-select>
</el-form-item>
</el-col>
@ -130,11 +134,20 @@
<el-input disabled v-model="form.projectName" placeholder="请输入工程名称" />
</el-form-item>
</el-col>
<el-col :span="12" :offset="0"
><el-form-item label="材料名称" prop="materialName">
<el-input v-model="form.materialName" placeholder="请输入设备材料名称" clearable /> </el-form-item
></el-col>
<el-col :span="12" v-if="form.materialSource == '2'">
<el-form-item label="合同编号" prop="contractName">
<el-select v-model="form.contractName" filterable placeholder="请选择合同" style="width: 100%">
<el-option v-for="item in contractNameList" :key="item.contractCode" :label="item.contractCode"
:value="item.contractCode"></el-option>
<el-option
v-for="item in contractNameList"
:key="item.contractCode"
:label="item.contractCode"
:value="item.contractCode"
></el-option>
</el-select>
</el-form-item>
</el-col>
@ -149,48 +162,59 @@
<div class="detail">
<div class="detail-header">
<span>数量验收</span>
<el-button type="primary" v-if="form.materialSource == '1'" link @click="addItem"
icon="Plus">添加数量验收</el-button>
<el-button type="primary" v-if="form.materialSource == '1'" link @click="addItem" icon="Plus">添加数量验收</el-button>
</div>
<div v-for="(item, index) in form.itemList" :key="item.id" class="detail-item">
<el-row>
<el-col :span="12">
<el-form-item label="名称" :prop="`itemList.${index}.name`"
:rules="{ required: true, message: '名称不能为空', trigger: 'blur' }">
<el-form-item label="名称" :prop="`itemList.${index}.name`" :rules="{ required: true, message: '名称不能为空', trigger: 'blur' }">
<el-input :disabled="form.materialSource == '2'" v-model="item.name" placeholder="请输入名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="规格" :prop="`itemList.${index}.specification`"
:rules="{ required: true, message: '规格不能为空', trigger: 'blur' }">
<el-input :disabled="form.materialSource == '2'" v-model="item.specification"
placeholder="请输入规格" />
<el-form-item
label="规格"
:prop="`itemList.${index}.specification`"
:rules="{ required: true, message: '规格不能为空', trigger: 'blur' }"
>
<el-input :disabled="form.materialSource == '2'" v-model="item.specification" placeholder="请输入规格" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="单位" :prop="`itemList.${index}.unit`"
:rules="{ required: true, message: '单位不能为空', trigger: 'blur' }">
<el-form-item label="单位" :prop="`itemList.${index}.unit`" :rules="{ required: true, message: '单位不能为空', trigger: 'blur' }">
<el-input :disabled="form.materialSource == '2'" v-model="item.unit" placeholder="请输入单位" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="数量" :prop="`itemList.${index}.quantity`" :rules="rules.quantityRule"
ref="quantityFormItemRefs[index]">
<el-input :disabled="form.materialSource == '2'" type="number" v-model.number="item.quantity"
placeholder="请输入数量" min="0" @input="handleQuantityInput(index)"
@blur="handleQuantityBlur(index)" />
<el-form-item label="数量" :prop="`itemList.${index}.quantity`" :rules="rules.quantityRule" ref="quantityFormItemRefs[index]">
<el-input
:disabled="form.materialSource == '2'"
type="number"
v-model.number="item.quantity"
placeholder="请输入数量"
min="0"
@input="handleQuantityInput(index)"
@blur="handleQuantityBlur(index)"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="验收" :prop="`itemList.${index}.acceptedQuantity`"
:rules="rules.acceptedQuantityRule">
<el-input type="number" v-model.number="item.acceptedQuantity" placeholder="请输入验收" min="0"
@input="handleAcceptedInput(index)" />
<el-form-item label="验收" :prop="`itemList.${index}.acceptedQuantity`" :rules="rules.acceptedQuantityRule">
<el-input
type="number"
v-model.number="item.acceptedQuantity"
placeholder="请输入验收"
min="0"
@input="handleAcceptedInput(index)"
/>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="缺件" :prop="`itemList.${index}.shortageQuantity`"
:rules="{ required: true, message: '缺件数量不能为空', trigger: 'blur' }">
<el-form-item
label="缺件"
:prop="`itemList.${index}.shortageQuantity`"
:rules="{ required: true, message: '缺件数量不能为空', trigger: 'blur' }"
>
<el-input type="number" min="0" v-model="item.shortageQuantity" placeholder="自动计算" readonly />
<span class="tips">*自动计算数量-验收数量</span>
</el-form-item>
@ -212,26 +236,22 @@
<el-col :span="12">
<el-form-item label="合格证文件" prop="certCountFileId">
<file-upload :isShowTip="false" :fileType="['pdf', 'png', 'jpg', 'jpeg']"
v-model="form.certCountFileId" />
<file-upload :isShowTip="false" :fileType="['pdf', 'png', 'jpg', 'jpeg']" v-model="form.certCountFileId" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="出厂报告文件" prop="reportCountFileId">
<file-upload :isShowTip="false" :fileType="['pdf', 'png', 'jpg', 'jpeg']"
v-model="form.reportCountFileId" />
<file-upload :isShowTip="false" :fileType="['pdf', 'png', 'jpg', 'jpeg']" v-model="form.reportCountFileId" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="技术资料文件" prop="techDocCountFileId">
<file-upload :isShowTip="false" :fileType="['pdf', 'png', 'jpg', 'jpeg']"
v-model="form.techDocCountFileId" />
<file-upload :isShowTip="false" :fileType="['pdf', 'png', 'jpg', 'jpeg']" v-model="form.techDocCountFileId" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="厂家资质文件" prop="licenseCountFileId">
<file-upload :isShowTip="false" :fileType="['pdf', 'png', 'jpg', 'jpeg']"
v-model="form.licenseCountFileId" />
<file-upload :isShowTip="false" :fileType="['pdf', 'png', 'jpg', 'jpeg']" v-model="form.licenseCountFileId" />
</el-form-item>
</el-col>
<el-col :span="24">
@ -412,7 +432,7 @@ const { queryParams, form, rules } = toRefs(data);
const getList = async () => {
loading.value = true;
try {
if(queryParams.value.materialSource=='0'){
if (queryParams.value.materialSource == '0') {
delete queryParams.value.materialSource;
}
const res = await listMaterialReceive(queryParams.value);
@ -506,9 +526,9 @@ const handleUpdate = async (row?: MaterialReceiveVO) => {
watchItemChanges(index);
// 手动触发当前条目的数量、验收数量、缺件数量验证
if (materialReceiveFormRef.value) {
materialReceiveFormRef.value.validateField(`itemList.${index}.quantity`, () => { });
materialReceiveFormRef.value.validateField(`itemList.${index}.acceptedQuantity`, () => { });
materialReceiveFormRef.value.validateField(`itemList.${index}.shortageQuantity`, () => { });
materialReceiveFormRef.value.validateField(`itemList.${index}.quantity`, () => {});
materialReceiveFormRef.value.validateField(`itemList.${index}.acceptedQuantity`, () => {});
materialReceiveFormRef.value.validateField(`itemList.${index}.shortageQuantity`, () => {});
}
});
@ -619,9 +639,9 @@ const handleAcceptedInput = (index: number) => {
// 手动验证数量和验收数量字段
const validateQuantityField = (index: number) => {
if (materialReceiveFormRef.value) {
materialReceiveFormRef.value.validateField(`itemList.${index}.quantity`, () => { });
materialReceiveFormRef.value.validateField(`itemList.${index}.acceptedQuantity`, () => { });
materialReceiveFormRef.value.validateField(`itemList.${index}.shortageQuantity`, () => { });
materialReceiveFormRef.value.validateField(`itemList.${index}.quantity`, () => {});
materialReceiveFormRef.value.validateField(`itemList.${index}.acceptedQuantity`, () => {});
materialReceiveFormRef.value.validateField(`itemList.${index}.shortageQuantity`, () => {});
}
};

View File

@ -1,22 +1,20 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<div class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="材料名称" prop="materialsName">
<el-input v-model="queryParams.materialsName" placeholder="请输入材料名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" v-hasPermi="['materials:materialsInventory:list']" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" v-hasPermi="['materials:materialsInventory:list']" @click="resetQuery">重置</el-button>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
@ -25,273 +23,179 @@
导出
</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getTableList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="materialsInventoryList" @expand-change="handleExpandChange">
<el-table-column type="expand">
<template #default="props">
<div style="margin-left: 60px">
<el-table :data="materialsUseRecordList" border v-loading="loadingChild">
<el-table-column label="序号" align="center" type="index" width="60" />
<el-table-column label="使用数量" align="center" prop="useNumber" />
<el-table-column label="剩余量" align="center" prop="residueNumber" />
<el-table-column label="使用部位" align="center" prop="usePart" />
<el-table-column label="备注" align="center" prop="remark" />
</el-table>
<pagination
v-show="totalChild > 0"
:total="totalChild"
v-model:page="queryParamsChild.pageNum"
v-model:limit="queryParamsChild.pageSize"
@pagination="getListChild"
/>
</div>
</template>
<el-table :data="tableData" row-key="id" border default-expand-all class="custom-table">
<el-table-column prop="materialsName" label="物资名称" align="left" class-name="inventory-register" />
<el-table-column prop="quantityCount" label="计划数量" align="center" class-name="inventory-register" />
<el-table-column label="入库登记" align="center" class-name="inventory-register">
<el-table-column prop="supplier" label="供货单位" align="center" class-name="inventory-register" />
<el-table-column prop="number" label="数量" align="center" class-name="inventory-register" />
<el-table-column prop="operator" label="签收人" align="center" class-name="inventory-register" />
<el-table-column prop="enterTime" label="日期" align="center" class-name="inventory-register" />
</el-table-column>
<!-- <el-table-column type="selection" width="55" align="center" /> -->
<el-table-column label="序号" type="index" width="60" align="center" />
<el-table-column label="材料名称" align="center" prop="equipmentMaterialsName" />
<el-table-column label="入库登记" align="center">
<el-table-column label="数量" align="center" prop="number">
<template #default="scope">
<span>{{ scope.row.inventory.number }}</span>
</template>
</el-table-column>
<el-table-column label="签收人" align="center" prop="operator">
<template #default="scope">
<span>{{ scope.row.inventory.operator }}</span>
</template>
</el-table-column>
<el-table-column label="日期" align="center" prop="outPutTime" width="160">
<template #default="scope">
<span>{{ parseTime(scope.row.inventory.outPutTime, '{y}年{m}月{d}日') }}</span>
</template>
</el-table-column>
<el-table-column label="出库登记" align="center" class-name="out-register">
<el-table-column prop="recipient" label="接受单位" align="center" class-name="out-register" />
<el-table-column prop="outNumber" label="数量" align="center" class-name="out-register" />
<el-table-column prop="outOperator" label="出库人" align="center" class-name="out-register" />
<el-table-column prop="shipper" label="领用人" align="center" class-name="out-register" />
<el-table-column prop="createTime" label="领用日期" align="center" class-name="out-register" />
</el-table-column>
<el-table-column label="出库登记" align="center" prop="outbound">
<el-table-column label="交接单位" align="center" prop="recipient">
<template #default="scope">
<span>{{ scope.row.outbound.recipient }}</span>
</template>
</el-table-column>
<el-table-column label="数量" align="center" prop="number">
<template #default="scope">
<span>{{ scope.row.outbound.number }}</span>
</template>
</el-table-column>
<el-table-column label="出库人" align="center" prop="operator">
<template #default="scope">
<span>{{ scope.row.outbound.operator }}</span>
</template>
</el-table-column>
<el-table-column label="领用人" align="center" prop="shipper">
<template #default="scope">
<span>{{ scope.row.outbound.shipper }}</span>
</template>
</el-table-column>
<el-table-column label="日期" align="center" prop="outPutTime" width="160">
<template #default="scope">
<span>{{ parseTime(scope.row.outbound.outPutTime, '{y}年{m}月{d}日') }}</span>
</template>
</el-table-column>
<el-table-column label="到货剩余登记" align="center" class-name="out-register">
<el-table-column prop="residue" label="剩余量" align="center" class-name="out-register" />
<el-table-column prop="disposition" label="处理方式" align="center" class-name="out-register" />
</el-table-column>
<el-table-column label="剩余量" align="center" prop="residue" />
<el-table-column label="备注" align="center" prop="remark" />
<!-- <el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-space wrap>
<el-button link type="success" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['materials:materialsInventory:edit']">
修改
</el-button>
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['materials:materialsInventory:remove']">
删除
</el-button>
</el-space>
</template>
</el-table-column> -->
<el-table-column label="使用登记" align="center" class-name="use-register">
<el-table-column prop="usePart" label="使用部位" align="center" class-name="use-register" />
<el-table-column prop="useNumber" label="使用数量" align="center" class-name="use-register" />
<el-table-column prop="useTime" label="使用日期" align="center" class-name="use-register" />
<el-table-column prop="residueNumber" label="剩余量" align="center" class-name="use-register" />
</el-table-column>
<el-table-column label="备注" align="center" prop="remark" class-name="use-register" />
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getTableList"
/>
</el-card>
<!-- 添加或修改材料出/入库对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="materialsInventoryFormRef" :model="form" :rules="rules" label-width="120px">
<el-form-item label="出入库状态" prop="outPut">
<el-select v-model="form.outPut" clearable placeholder="请输入出入库状态">
<el-option v-for="item in out_put_type" :key="item.value" :label="item.label" :value="item.value" />
</el-select>
</el-form-item>
<el-form-item label="出/入库的数量" prop="number">
<el-input v-model="form.number" placeholder="请输入出/入库的数量" />
</el-form-item>
<el-form-item label="出/入库操作时间" prop="outPutTime">
<el-date-picker clearable v-model="form.outPutTime" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="请选择出/入库操作时间">
</el-date-picker>
</el-form-item>
<el-form-item label="剩余库存数量" prop="residue">
<el-input v-model="form.residue" placeholder="请输入剩余库存数量" />
</el-form-item>
<el-form-item label="操作人" prop="operator">
<el-input v-model="form.operator" placeholder="请输入操作人" />
</el-form-item>
<el-form-item label="材料出入证明" prop="path">
<el-input v-model="form.path" type="textarea" placeholder="请输入内容" />
</el-form-item>
<el-form-item label="处理方式" prop="disposition">
<el-input v-model="form.disposition" placeholder="请输入处理方式" />
</el-form-item>
<el-form-item label="交接单位" prop="recipient">
<el-input v-model="form.recipient" placeholder="请输入交接单位" />
</el-form-item>
<el-form-item label="领用人" prop="shipper">
<el-input v-model="form.shipper" placeholder="请输入领用人" />
</el-form-item>
<el-form-item label="备注" prop="remark">
<el-input v-model="form.remark" placeholder="请输入备注" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="MaterialsInventory" lang="ts">
import {
addMaterialsInventory,
delMaterialsInventory,
getMaterialsInventory,
listMaterialsInventory,
updateMaterialsInventory
} from '@/api/materials/materialsInventory';
import { MaterialsInventoryForm, MaterialsInventoryQuery, MaterialsInventoryVO } from '@/api/materials/materialsInventory/types';
import { useUserStoreHook } from '@/store/modules/user';
import { listMaterialsUseRecord } from '@/api/materials/materialsUseRecord';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { out_put_type } = toRefs<any>(proxy?.useDict('out_put_type'));
<script setup lang="ts">
import useUserStore from '@/store/modules/user';
import { getLedgerList } from '@/api/materials/materialsInventory';
// 获取用户 store
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const { proxy } = getCurrentInstance() as any;
const userStore = useUserStore();
const currentProject = computed(() => userStore.selectedProject);
const materialsInventoryList = ref<MaterialsInventoryVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const queryFormRef = ref();
const queryParams = ref({
materialsName: '',
pageNum: 1,
pageSize: 10
});
const total = ref(0);
const loadingChild = ref(true);
const totalChild = ref(0);
const materialsUseRecordList = ref<any[]>([]);
const queryFormRef = ref<ElFormInstance>();
const materialsInventoryFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: MaterialsInventoryForm = {
id: undefined,
materialsId: undefined,
projectId: currentProject.value.goId,
outPut: undefined,
number: undefined,
outPutTime: undefined,
residue: undefined,
operator: undefined,
path: undefined,
disposition: undefined,
recipient: undefined,
shipper: undefined,
remark: undefined
};
const data = reactive({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
materialsId: undefined,
projectId: currentProject.value.goId,
outPut: undefined,
number: undefined,
outPutTime: undefined,
residue: undefined,
operator: undefined,
path: undefined,
disposition: undefined,
recipient: undefined,
shipper: undefined,
params: {}
},
queryParamsChild: {
pageNum: 1,
pageSize: 10,
inventoryId: undefined,
usePart: undefined,
useNumber: undefined,
residueNumber: undefined
},
rules: {
id: [{ required: true, message: '主键id不能为空', trigger: 'blur' }],
materialsId: [{ required: true, message: '材料id不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules, queryParamsChild } = toRefs(data);
const materialsOptions = ref([]);
/** 处理外层表格展开/折叠记录当前展开行的inventoryId */
const handleExpandChange = (row: any) => {
queryParamsChild.value.inventoryId = row.id;
getListChild();
};
/** 查询材料子级登记列表(内层列表) */
const getListChild = async () => {
loadingChild.value = true;
try {
const res = await listMaterialsUseRecord(queryParamsChild.value);
materialsUseRecordList.value = res.rows;
totalChild.value = res.total;
} finally {
loadingChild.value = false;
const tableRef = ref();
const showSearch = ref(true);
const loading = ref(false);
const tableData = ref([]);
//获取列表
const getTableList = async () => {
const parmas = {
...queryParams.value,
projectId: currentProject.value.id
};
const res = await getLedgerList(parmas);
if (res.code === 200) {
const data = restructureData(res.rows);
tableData.value = data;
total.value = res.total;
}
};
/** 查询材料出/入库列表 */
const getList = async () => {
loading.value = true;
const res = await listMaterialsInventory(queryParams.value);
materialsInventoryList.value = res.data.list;
total.value = res.data.total;
const materialsMap = new Map();
materialsOptions.value = Array.from(materialsMap.values());
loading.value = false;
};
// 重组数据结构的函数
const restructureData = (data) => {
return data.map((item) => {
const newItem: any = {
id: item.id,
materialsName: item.materialsName,
quantityCount: item.quantityCount,
supplier: item.supplier,
number: item.number,
operator: item.operator,
enterTime: item.enterTime,
children: []
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
if (item.outList && item.outList.length > 0) {
// 处理第一个outList项目
const firstOut = item.outList[0];
newItem.recipient = firstOut.recipient;
newItem.outNumber = firstOut.number;
newItem.outOperator = firstOut.operator;
newItem.shipper = firstOut.shipper;
newItem.createTime = firstOut.createTime;
newItem.residue = firstOut.residue;
newItem.disposition = firstOut.disposition;
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
materialsInventoryFormRef.value?.resetFields();
};
// 处理第一个outList中的useList
if (firstOut.useList && firstOut.useList.length > 0) {
const firstUse = firstOut.useList[0];
newItem.usePart = firstUse.usePart;
newItem.useNumber = firstUse.useNumber;
newItem.useTime = firstUse.createTime;
newItem.residueNumber = firstUse.residueNumber;
newItem.remark = firstUse.remark;
// 将剩余的useList项目添加到children
for (let i = 1; i < firstOut.useList.length; i++) {
const useItem = firstOut.useList[i];
newItem.children.push({
id: useItem.id,
usePart: useItem.usePart,
useNumber: useItem.useNumber,
useTime: useItem.createTime,
residueNumber: useItem.residueNumber,
remark: useItem.remark,
type: 'use'
});
}
}
// 处理剩余的outList项目
for (let i = 1; i < item.outList.length; i++) {
const outItem = item.outList[i];
const outNode: any = {
id: outItem.id,
recipient: outItem.recipient,
outNumber: outItem.number,
outOperator: outItem.operator,
shipper: outItem.shipper,
createTime: outItem.createTime,
residue: outItem.residue,
disposition: outItem.disposition,
type: 'out',
children: []
};
// 处理当前outList项目中的useList
if (outItem.useList && outItem.useList.length > 0) {
const firstUse = outItem.useList[0];
outNode.usePart = firstUse.usePart;
outNode.useNumber = firstUse.useNumber;
outNode.useTime = firstUse.createTime;
outNode.residueNumber = firstUse.residueNumber;
outNode.remark = firstUse.remark;
// 将剩余的useList项目添加到children
for (let j = 1; j < outItem.useList.length; j++) {
const useItem = outItem.useList[j];
outNode.children.push({
id: useItem.id,
usePart: useItem.usePart,
useNumber: useItem.useNumber,
useTime: useItem.createTime,
residueNumber: useItem.residueNumber,
remark: useItem.remark,
type: 'use'
});
}
}
newItem.children.push(outNode);
}
}
return newItem;
});
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
getTableList();
};
/** 重置按钮操作 */
@ -300,75 +204,46 @@ const resetQuery = () => {
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: MaterialsInventoryVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 修改按钮操作 */
const handleUpdate = async (row?: MaterialsInventoryVO) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getMaterialsInventory(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改材料出/入库';
};
/** 提交按钮 */
const submitForm = () => {
materialsInventoryFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
form.value.projectId = currentProject.value?.id;
if (form.value.id) {
await updateMaterialsInventory(form.value).finally(() => (buttonLoading.value = false));
} else {
await addMaterialsInventory(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: MaterialsInventoryVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除材料出/入库编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delMaterialsInventory(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
//导出
const handleExport = () => {
proxy?.download(
'materials/materialsInventory/export',
'/materials/materials/export',
{
...queryParams.value
projectId: currentProject.value?.id
},
`materialsInventory_${new Date().getTime()}.xlsx`
`物资跟踪管理台账.xlsx`
);
};
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value.goId,
(nid, oid) => {
queryParams.value.projectId = nid;
form.value.projectId = nid;
getList();
}
);
onUnmounted(() => {
listeningProject();
});
onMounted(() => {
getList();
getTableList();
});
</script>
<style lang="scss">
/* 关键样式 - 确保选择器有足够优先级 */
.custom-table .el-table__body .inventory-register,
.custom-table .el-table__header .inventory-register {
background-color: #f0f9ff !important;
}
.custom-table .el-table__body .out-register,
.custom-table .el-table__header .out-register {
background-color: #f0fff3 !important;
}
.custom-table .el-table__body .use-register,
.custom-table .el-table__header .use-register {
background-color: #fffaf0 !important;
}
/* 表头样式增强 */
.el-table__header th {
background-color: #eef1f6;
font-weight: bold;
}
/* 操作按钮样式 */
.operation-buttons {
margin-bottom: 15px;
}
</style>

View File

@ -1,76 +1,101 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> </transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="使用部位" prop="usePart">
<el-input v-model="queryParams.usePart" placeholder="请输入使用部位" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-row :gutter="10" class="mb8">
<el-col :span="5">
<el-card shadow="never">
<el-tree style="max-width: 600px" :data="TreeData" :props="defaultProps" @node-click="handleNodeClick" />
</el-card>
</el-col>
<!-- 外层表格添加ref用于控制展开状态 -->
<el-table ref="outerTableRef" v-loading="loading" :data="materialsUseInventoryList" @expand-change="handleExpandChange" border>
<el-table-column type="expand">
<template #default="props">
<div style="margin-left: 60px">
<el-table :data="materialsUseRecordList" border v-loading="loadingChild">
<el-table-column label="序号" align="center" type="index" width="60" />
<el-table-column label="使用数量" align="center" prop="useNumber" />
<el-table-column label="剩余量" align="center" prop="residueNumber" />
<el-table-column label="使用部位" align="center" prop="usePart" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button
link
type="primary"
icon="delete"
v-if="scope.row.ishow"
@click="handleDelete(scope.row)"
v-hasPermi="['materials:materialsUseRecord:remove']"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<pagination
v-show="totalChild > 0"
:total="totalChild"
v-model:page="queryParamsChild.pageNum"
v-model:limit="queryParamsChild.pageSize"
@pagination="getListChild"
/>
</div>
<el-col :span="19">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave"> </transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="物资名称" prop="materialsName">
<el-input v-model="queryParams.materialsName" placeholder="请输入物资名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
</el-table-column>
<el-table-column label="序号" align="center" type="index" width="60" />
<el-table-column label="物资名称" align="center" prop="materialsName" />
<el-table-column label="交接单位" align="center" prop="recipient" />
<el-table-column label="计划数量" align="center" prop="quantityCount" />
<el-table-column label="数量" align="center" prop="number" />
<el-table-column label="出库人" align="center" prop="operator" />
<el-table-column label="领用人" align="center" prop="shipper" />
<el-table-column label="剩余量" align="center" prop="residue" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['materials:materialsUseRecord:add']"
>添加登记</el-button
>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 外层表格使用expandedRowKeys控制展开状态 -->
<el-table
ref="outerTableRef"
v-loading="loading"
:data="materialsUseInventoryList"
@expand-change="handleExpandChange"
border
:expanded-row-keys="expandedRowKeys"
>
<el-table-column type="expand">
<template #default="props">
<div style="margin-left: 60px">
<!-- 子表格使用当前行的独立数据 -->
<el-table :data="getChildData(props.row.id)" border v-loading="getChildLoading(props.row.id)">
<el-table-column label="序号" align="center" type="index" width="60" />
<el-table-column label="使用数量" align="center" prop="useNumber" />
<el-table-column label="剩余量" align="center" prop="residueNumber" />
<el-table-column label="使用部位" align="center" prop="usePart" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button
link
type="primary"
icon="delete"
v-if="scope.row.ishow"
@click="handleDelete(scope.row, props.row.id)"
v-hasPermi="['materials:materialsUseRecord:remove']"
>删除</el-button
>
</template>
</el-table-column>
</el-table>
<!-- 子分页使用当前行的独立分页参数 -->
<pagination
v-show="getChildTotal(props.row.id) > 0"
:total="getChildTotal(props.row.id)"
v-model:page="getChildQueryParams(props.row.id).pageNum"
v-model:limit="getChildQueryParams(props.row.id).pageSize"
@pagination="(page, limit) => handleChildPagination(props.row.id, page, limit)"
/>
</div>
</template>
</el-table-column>
<el-table-column label="序号" align="center" type="index" width="60" />
<el-table-column label="物资名称" align="center" prop="materialsName" />
<el-table-column label="交接单位" align="center" prop="recipient" />
<el-table-column label="计划数量" align="center" prop="quantityCount" />
<el-table-column label="数量" align="center" prop="number" />
<el-table-column label="出库人" align="center" prop="operator" />
<el-table-column label="领用人" align="center" prop="shipper" />
<el-table-column label="剩余量" align="center" prop="residue" />
<el-table-column label="备注" align="center" prop="remark" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-button link type="primary" icon="Plus" @click="handleAdd(scope.row)" v-hasPermi="['materials:materialsUseRecord:add']"
>添加登记</el-button
>
</template>
</el-table-column>
</el-table>
<pagination
v-show="total > 0"
:total="total"
v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize"
@pagination="getList"
/>
</el-card>
</el-col>
</el-row>
<!-- 添加或修改材料使用登记对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="materialsUseRecordFormRef" :model="form" :rules="rules" label-width="80px">
@ -104,39 +129,53 @@ import {
updateMaterialsUseRecord
} from '@/api/materials/materialsUseRecord';
import { MaterialsUseRecordVO, MaterialsUseRecordQuery, MaterialsUseRecordForm } from '@/api/materials/materialsUseRecord/types';
import { getCurrentInstance, ComponentInternalInstance, onMounted, ref, reactive, toRefs, computed } from 'vue';
import { getCurrentInstance, ComponentInternalInstance, onMounted, ref, reactive, toRefs, computed, watch, WatchStopHandle } from 'vue';
import { ElFormInstance, ElTable } from 'element-plus';
import useUserStore from '@/store/modules/user';
import { get } from 'lodash';
import { getMaterialsList } from '@/api/materials/materialOutbound';
// 类型定义补充若项目中无全局DialogOption类型需添加
// 类型定义补充
interface DialogOption {
visible: boolean;
title: string;
}
// 子列表状态接口:为每个父行存储独立数据
interface ChildRowState {
list: MaterialsUseRecordVO[];
queryParams: MaterialsUseRecordQuery & {
pageNum: number;
pageSize: number;
inventoryId: number;
projectId?: number;
};
loading: boolean;
total: number;
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const userStore = useUserStore();
const currentProject = computed(() => userStore.selectedProject);
// 核心数据响应式定义
const materialsUseRecordList = ref<MaterialsUseRecordVO[]>([]);
const materialsUseInventoryList = ref<any[]>([]); // 外层列表数据类型可根据实际接口返回调整
const materialsUseInventoryList = ref<any[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const loadingChild = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const totalChild = ref(0);
const expandedRowKeys = ref<number[]>([]); // 控制展开行的ID集合
// 子列表状态管理使用对象存储不同父行的子数据key为inventoryId
const childRowStates = ref<Record<number, ChildRowState>>({});
// 组件Ref定义
const queryFormRef = ref<ElFormInstance>();
const materialsUseRecordFormRef = ref<ElFormInstance>();
const outerTableRef = ref<InstanceType<typeof ElTable> | undefined>(undefined); // 外层表格Ref控制展开
const currentExpandInventoryId = ref<number | undefined>(undefined); // 存储当前展开行的inventoryId
const outerTableRef = ref<InstanceType<typeof ElTable> | undefined>(undefined);
const currentOperateInventoryId = ref<number | undefined>(undefined); // 当前操作的父行ID
// 对话框与表单数据
const dialog = reactive<DialogOption>({
@ -160,72 +199,159 @@ const data = reactive({
pageSize: 10,
projectId: currentProject.value?.id,
outPut: 1,
usePart: undefined // 补充usePart字段与表单prop对应
} as MaterialsUseRecordQuery & { outPut?: number; usePart?: string },
queryParamsChild: {
pageNum: 1,
pageSize: 10,
inventoryId: undefined,
materialsName: undefined,
usePart: undefined,
useNumber: undefined,
residueNumber: undefined
} as MaterialsUseRecordQuery,
materialsId: ''
} as MaterialsUseRecordQuery & {
outPut?: number;
materialsName?: string;
usePart?: string;
},
rules: {
id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
projectId: [{ required: true, message: '项目ID不能为空', trigger: 'blur' }],
inventoryId: [{ required: true, message: '库存ID不能为空', trigger: 'blur' }],
usePart: [{ required: true, message: '使用部位不能为空', trigger: 'blur' }],
useNumber: [{ required: true, message: '使用数量不能为空', trigger: 'blur' }],
residueNumber: [{ required: true, message: '剩余量不能为空', trigger: 'blur' }]
useNumber: [{ required: true, message: '使用数量不能为空', trigger: 'blur' }]
}
});
const { queryParams, form, rules, queryParamsChild } = toRefs(data);
const { form, rules } = toRefs(data);
const queryParams = ref<typeof data.queryParams>({ ...data.queryParams });
const TreeData: any = ref([]);
//获取材料列表
const getMaterialsListData = async () => {
const res = await getMaterialsList({ projectId: currentProject.value?.id, pageNum: 1, pageSize: 999 });
if (res.code === 200) {
// 将数据转换为树形结构
TreeData.value = res.rows.map((item) => ({
...item,
label: item.materialsName + '_' + item.createTime,
children: []
}));
queryParams.value.materialsId = TreeData.value[0].id;
getList();
}
};
/** 查询材料使用登记列表(外层列表) */
const handleNodeClick = (data: any) => {
console.log(data);
queryParams.value.materialsId = data.id;
getList();
};
const defaultProps = {
children: 'children',
label: 'label'
};
// ------------------------------ 子列表状态工具函数 ------------------------------
/** 获取或初始化指定父行的子列表状态 */
const getOrInitChildState = (inventoryId: number): ChildRowState => {
if (!childRowStates.value[inventoryId]) {
childRowStates.value[inventoryId] = {
list: [],
loading: false,
total: 0,
queryParams: {
pageNum: 1,
pageSize: 10,
inventoryId,
projectId: currentProject.value?.id
}
};
}
return childRowStates.value[inventoryId];
};
/** 获取指定父行的子列表数据 */
const getChildData = (inventoryId: number): MaterialsUseRecordVO[] => {
return getOrInitChildState(inventoryId).list;
};
/** 获取指定父行的子列表加载状态 */
const getChildLoading = (inventoryId: number): boolean => {
return getOrInitChildState(inventoryId).loading;
};
/** 获取指定父行的子列表总条数 */
const getChildTotal = (inventoryId: number): number => {
return getOrInitChildState(inventoryId).total;
};
/** 获取指定父行的子分页参数 */
const getChildQueryParams = (inventoryId: number) => {
return getOrInitChildState(inventoryId).queryParams;
};
// ------------------------------ 核心业务逻辑 ------------------------------
/** 查询外层列表数据 */
const getList = async () => {
loading.value = true;
try {
const res = await listMaterialsUseInventory(queryParams.value);
materialsUseInventoryList.value = res.rows;
total.value = res.total;
// 保持已展开行的状态
if (expandedRowKeys.value.length > 0 && outerTableRef.value) {
expandedRowKeys.value.forEach((id) => {
const targetRow = materialsUseInventoryList.value.find((item) => item.id === id);
targetRow && outerTableRef.value!.toggleRowExpansion(targetRow, true);
});
}
} finally {
loading.value = false;
}
};
/** 处理外层表格展开/折叠记录当前展开行的inventoryId */
const handleExpandChange = (row: any) => {
currentExpandInventoryId.value = row.id; // 记录展开行ID
queryParamsChild.value.inventoryId = row.id;
getListChild();
/** 处理外层表格展开/折叠 */
const handleExpandChange = async (row: any, expandedRows: any[]) => {
const inventoryId = row.id;
// 更新展开行ID集合
expandedRowKeys.value = expandedRows.map((item) => item.id);
// 展开时加载子数据(仅首次加载)
const childState = getOrInitChildState(inventoryId);
if (expandedRows.some((item) => item.id === inventoryId) && childState.list.length === 0) {
await getListChild(inventoryId);
}
};
/** 查询材料子级登记列表(内层列表) */
const getListChild = async () => {
loadingChild.value = true;
/** 查询指定父行的子列表数据 */
const getListChild = async (inventoryId: number) => {
const childState = getOrInitChildState(inventoryId);
childState.loading = true;
try {
const res = await listMaterialsUseRecord(queryParamsChild.value);
materialsUseRecordList.value = res.rows;
const res = await listMaterialsUseRecord(childState.queryParams);
childState.list = res.rows;
// 控制首行删除按钮显示
if (res.rows.length > 0) {
materialsUseRecordList.value[0].ishow = true;
if (childState.list.length > 0) {
childState.list[0].ishow = true;
}
totalChild.value = res.total;
childState.total = res.total;
} finally {
loadingChild.value = false;
childState.loading = false;
}
};
/** 处理子列表分页变化 */
const handleChildPagination = async (inventoryId: number, page: number, limit: number) => {
const childState = getOrInitChildState(inventoryId);
childState.queryParams.pageNum = page;
childState.queryParams.pageSize = limit;
await getListChild(inventoryId);
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
currentOperateInventoryId.value = undefined;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
form.value = { ...initFormData, projectId: currentProject.value?.id };
materialsUseRecordFormRef.value?.resetFields();
};
@ -238,10 +364,16 @@ const handleQuery = () => {
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
queryParams.value = {
...data.queryParams,
pageNum: 1,
pageSize: 10,
projectId: currentProject.value?.id
};
handleQuery();
};
/** 多选框选中数据(当前模板未使用,保留原逻辑) */
/** 多选框选中数据处理 */
const handleSelectionChange = (selection: MaterialsUseRecordVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length !== 1;
@ -252,76 +384,87 @@ const handleSelectionChange = (selection: MaterialsUseRecordVO[]) => {
const handleAdd = async (row: any) => {
reset();
form.value.inventoryId = row.id;
currentOperateInventoryId.value = row.id;
dialog.visible = true;
dialog.title = '添加材料使用登记';
};
/** 修改按钮操作(当前模板未使用,保留原逻辑) */
/** 修改按钮操作 */
const handleUpdate = async (row?: MaterialsUseRecordVO) => {
if (!row && ids.value.length === 0) return;
reset();
const _id = row?.id || ids.value[0];
const res = await getMaterialsUseRecord(_id);
Object.assign(form.value, res.data);
currentOperateInventoryId.value = form.value.inventoryId;
dialog.visible = true;
dialog.title = '修改材料使用登记';
};
/** 提交按钮:核心优化逻辑(刷新外层列表+保留展开状态) */
/** 提交表单操作 */
const submitForm = () => {
materialsUseRecordFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
try {
// 1. 执行添加/修改接口请求
await addMaterialsUseRecord(form.value);
// 2. 刷新外层列表(确保外层数据同步,如剩余量)
await getList();
// 3. 刷新当前展开行的子列表
await getListChild();
// 4. 恢复展开状态根据记录的inventoryId重新展开行
if (currentExpandInventoryId.value && outerTableRef.value) {
const targetRow = materialsUseInventoryList.value.find((item) => item.id === currentExpandInventoryId.value);
if (targetRow) {
outerTableRef.value.toggleRowExpansion(targetRow, true);
}
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
} finally {
buttonLoading.value = false;
}
if (!valid || !currentOperateInventoryId.value) return;
buttonLoading.value = true;
try {
// 执行添加或修改操作
const apiFn = form.value.id ? updateMaterialsUseRecord : addMaterialsUseRecord;
await apiFn(form.value);
// 刷新外层列表和当前操作行的子列表
await getList();
await getListChild(currentOperateInventoryId.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
} finally {
buttonLoading.value = false;
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: MaterialsUseRecordVO) => {
const _ids = row?.id || ids.value;
/** 删除操作 */
const handleDelete = async (row: MaterialsUseRecordVO, inventoryId: number) => {
try {
await proxy?.$modal.confirm(`是否确认删除材料使用登记编号为"${_ids}"的数据项`);
await delMaterialsUseRecord(_ids);
await proxy?.$modal.confirm(`是否确认删除该记录`);
await delMaterialsUseRecord(row.id);
await getList();
await getListChild();
if (currentExpandInventoryId.value && outerTableRef.value) {
const targetRow = materialsUseInventoryList.value.find((item) => item.id === currentExpandInventoryId.value);
if (targetRow) {
outerTableRef.value.toggleRowExpansion(targetRow, true);
}
}
await getListChild(inventoryId);
proxy?.$modal.msgSuccess('删除成功');
await getListChild();
} finally {
loading.value = false;
} catch (error) {
// 取消删除时不做处理
}
};
/** 导出按钮操作(保留原逻辑) */
/** 导出操作 */
const handleExport = () => {
proxy?.download('materials/materialsUseRecord/export', { ...queryParams.value }, `materialsUseRecord_${new Date().getTime()}.xlsx`);
};
/** 页面挂载时加载外层列表 */
// 监听项目ID变化刷新数据
const listeningProject: WatchStopHandle = watch(
() => currentProject.value?.id,
(newId) => {
queryParams.value.projectId = newId;
form.value.projectId = newId;
// 更新所有子列表的项目ID
Object.values(childRowStates.value).forEach((state) => {
state.queryParams.projectId = newId;
});
getList();
}
);
// 页面卸载时清理监听
onUnmounted(() => {
listeningProject();
});
// 页面挂载时加载数据
onMounted(() => {
getList();
// getList();
getMaterialsListData();
});
</script>

View File

@ -0,0 +1,619 @@
<template>
<div class="overall-plan-material-supply">
<el-card shadow="always">
<template #header>
<el-row :gutter="10" class="mb8">
<el-form :inline="true">
<el-form-item v-if="state.masterData.status == 'draft'">
<el-button type="primary" icon="edit" @click="clickApprovalSheet1()">审批</el-button>
</el-form-item>
<el-form-item v-if="state.masterData.status == 'waiting' || state.masterData.status == 'finish'">
<el-button icon="view" @click="lookApprovalFlow()" type="warning">查看流程</el-button>
</el-form-item>
<el-form-item>
<file-upload
upload-url="/design/totalsupplyplan/import"
v-model="file"
:limit="1"
:file-type="['xls', 'xlsx']"
:on-upload-success="handleSuccess"
>
<el-button :disabled="state.masterData.status == 'finish'" type="primary" plain icon="upload">导入</el-button>
</file-upload>
</el-form-item>
<el-form-item>
<el-button type="primary" plain icon="Download" @click="handleExport">导出</el-button>
</el-form-item>
<el-form-item>
<el-button icon="Finished" @click="editApprovalSheet()" type="success" :disabled="state.masterData.status == 'finish'"
>一键全部保存</el-button
>
</el-form-item>
<el-form-item>
<el-button type="primary" @click="toggleExpandAll">{{ isExpandAll ? '一键收起' : '一键展开' }}</el-button>
</el-form-item>
</el-form>
<right-toolbar @queryTable="getMasterDataList"></right-toolbar>
</el-row>
</template>
</el-card>
<!-- 本地数据懒加载表格 -->
<el-table
:data="state.tableData"
v-loading="state.loading.list"
ref="tableRef"
stripe
style="width: 100%; margin-bottom: 20px; height: calc(100vh - 230px)"
row-key="id"
border
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:lazy="true"
:load="loadLocalChildNodes"
@expand-change="handleExpandChange"
>
<el-table-column align="center" prop="num" label="编号" />
<el-table-column prop="name" label="工程或费用名称" width="180" />
<el-table-column prop="unit" label="单位" />
<el-table-column prop="specification" label="规格型号" width="80" />
<el-table-column prop="quantity" label="数量" width="100" />
<el-table-column prop="batchNumber" label="批次号" width="200" />
<!-- 优化的输入框列 -->
<el-table-column prop="brand" label="品牌">
<template #default="{ row }">
<el-input
v-model.lazy="row.brand"
:disabled="state.masterData.status != 'draft'"
v-if="!row.hasChildren"
placeholder=""
clearable
:key="`brand-${row.id}`"
@change="handleInputChange(row, 'brand')"
/>
</template>
</el-table-column>
<el-table-column prop="texture" label="材质">
<template #default="{ row }">
<el-input
v-model.lazy="row.texture"
:disabled="state.masterData.status != 'draft'"
v-if="!row.hasChildren"
placeholder=""
clearable
:key="`texture-${row.id}`"
@change="handleInputChange(row, 'texture')"
/>
</template>
</el-table-column>
<el-table-column prop="qualityStandard" label="质量标准">
<template #default="{ row }">
<el-input
:disabled="state.masterData.status != 'draft'"
v-model.lazy="row.qualityStandard"
v-if="!row.hasChildren"
placeholder=""
clearable
:key="`qualityStandard-${row.id}`"
@change="handleInputChange(row, 'qualityStandard')"
/>
</template>
</el-table-column>
<el-table-column prop="partUsed" label="使用部位">
<template #default="{ row }">
<el-input
:disabled="state.masterData.status != 'draft'"
v-model.lazy="row.partUsed"
v-if="!row.hasChildren"
placeholder=""
clearable
:key="`partUsed-${row.id}`"
@change="handleInputChange(row, 'partUsed')"
/>
</template>
</el-table-column>
<el-table-column prop="deliveryPoints" label="交货地点">
<template #default="{ row }">
<el-input
:disabled="state.masterData.status != 'draft'"
v-model.lazy="row.deliveryPoints"
v-if="!row.hasChildren"
placeholder=""
clearable
:key="`deliveryPoints-${row.id}`"
@change="handleInputChange(row, 'deliveryPoints')"
/>
</template>
</el-table-column>
<el-table-column label="预计使用日期" width="150">
<template #default="{ row }">
<el-date-picker
v-model="row.dateService"
v-if="!row.hasChildren"
type="date"
value-format="YYYY-MM-DD"
:disabled="state.masterData.status != 'draft'"
placeholder="请选择日期"
:key="`dateService-${row.id}`"
@change="handleInputChange(row, 'dateService')"
/>
</template>
</el-table-column>
<el-table-column prop="remark" label="备注" />
</el-table>
<!-- 编辑弹窗 -->
<el-dialog v-model="visible" title="修改物料信息" :width="800" :close-on-click-modal="false" @close="handleClose">
<el-form ref="formRef" :model="formData" :rules="formRules" label-width="120px" class="space-y-4">
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="批次号" prop="batchNumber">
<el-input disabled v-model="formData.batchNumber" placeholder="请输入批次号" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="品牌" prop="brand">
<el-input v-model="formData.brand" placeholder="请输入品牌" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="材质" prop="texture">
<el-input v-model="formData.texture" placeholder="请输入材质" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="质量标准" prop="qualityStandard">
<el-input v-model="formData.qualityStandard" placeholder="请输入质量标准" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="使用部位" prop="partUsed">
<el-input v-model="formData.partUsed" placeholder="请输入使用部位" clearable />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="交货地点" prop="deliveryPoints">
<el-input v-model="formData.deliveryPoints" placeholder="请输入交货地点" clearable />
</el-form-item>
</el-col>
</el-row>
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="预计使用日期" prop="dateService">
<el-date-picker v-model="formData.dateService" type="date" placeholder="选择预计使用日期" format="YYYY-MM-DD" />
</el-form-item>
</el-col>
</el-row>
<el-row>
<el-col :span="24">
<el-form-item label="备注" prop="remark">
<el-input v-model="formData.remark" placeholder="请输入备注信息" type="textarea" rows="3" />
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<el-button @click="handleClose">取消</el-button>
<el-button type="primary" @click="handleSubmit">确定</el-button>
</template>
</el-dialog>
</div>
</template>
<script setup name="billofQuantities">
import { ref, reactive, onMounted, computed, getCurrentInstance, watch, onUnmounted } from 'vue';
import { ElMessage, ElLoading } from 'element-plus';
import {
obtainMasterDataList,
totalsupplyplan,
totalSupplyplanDetails,
materialChangeSupplyplan,
totalSupplyplanBatchEdit
} from '@/api/materials/overallPlanMaterialSupply/index';
import { useUserStoreHook } from '@/store/modules/user';
// 全局状态与实例获取
const userStore = useUserStoreHook();
const currentProject = computed(() => userStore.selectedProject);
const { proxy } = getCurrentInstance();
// 基础状态管理
const visible = ref(false);
const formRef = ref(null);
const tableRef = ref(null);
const file = ref(null);
const isExpandAll = ref(false);
const expandRowKeys = ref([]);
const editDataMap = ref(new Map()); // 用Map存储修改的数据提高查询效率
const loadingInstance = ref(null);
const fullTreeData = ref([]); // 存储完整树形数据,用于本地懒加载
// 页面核心状态
const state = reactive({
tableData: [], // 初始只存放根节点
queryForm: {
projectId: currentProject.value?.id,
versions: '',
sheet: '',
pageSize: 20,
pageNum: 1
},
loading: {
versions: false,
sheets: false,
list: false
},
masterData: {}
});
// 表单数据
const formData = reactive({
batchNumber: '',
brand: '',
compileDate: '',
dateService: '',
deliveryPoints: '',
id: undefined,
name: '',
num: '',
partUsed: '',
planNumber: '',
projectId: undefined,
qualityStandard: '',
quantity: 0,
remark: '',
specification: '',
status: '',
texture: '',
unit: ''
});
// 表单验证规则
const formRules = reactive({
name: [
{ required: true, message: '请输入名称', trigger: 'blur' },
{ max: 100, message: '名称长度不能超过100个字符', trigger: 'blur' }
],
num: [
{ required: true, message: '请输入编号', trigger: 'blur' },
{ max: 50, message: '编号长度不能超过50个字符', trigger: 'blur' }
],
quantity: [
{ required: true, message: '请输入数量', trigger: 'blur' },
{ type: 'number', min: 0, message: '数量不能为负数', trigger: 'blur' }
],
compileDate: [{ required: true, message: '请选择编制日期', trigger: 'change' }]
});
/**
* 本地数据懒加载实现:从完整数据中筛选子节点
*/
const loadLocalChildNodes = (row, treeNode, resolve) => {
// 避免重复加载
if (row.children && row.children.length > 0) {
resolve(row.children);
return;
}
// 显示加载状态
loadingInstance.value = ElLoading.service({
target: tableRef.value.$el,
text: '加载子节点中...',
background: 'rgba(255, 255, 255, 0.8)'
});
try {
// 从完整数据中筛选当前行的子节点假设父ID对应子节点的pid
const childNodes = fullTreeData.value.filter((node) => node.pid === row.id);
// 标记子节点是否有子节点
const formattedChildren = childNodes.map((node) => ({
...node,
hasChildren: fullTreeData.value.some((child) => child.pid === node.id)
}));
// 缓存子节点到当前行
row.children = formattedChildren;
resolve(formattedChildren);
} catch (error) {
ElMessage.error(`加载子节点异常:${error.message}`);
resolve([]);
} finally {
loadingInstance.value?.close();
}
};
/**
* 处理展开状态变化
*/
const handleExpandChange = (row, expanded) => {
if (expanded) {
if (!expandRowKeys.value.includes(row.id)) {
expandRowKeys.value.push(row.id);
}
} else {
expandRowKeys.value = expandRowKeys.value.filter((id) => id !== row.id);
}
};
/**
* 一键展开/收起
*/
const toggleExpandAll = async () => {
isExpandAll.value = !isExpandAll.value;
if (isExpandAll.value) {
loadingInstance.value = ElLoading.service({
target: tableRef.value.$el,
text: '正在加载所有节点...',
background: 'rgba(255, 255, 255, 0.8)'
});
try {
// 递归加载所有层级子节点
const loadAllChildren = (nodes) => {
nodes.forEach((node) => {
const children = fullTreeData.value.filter((child) => child.sid === node.id);
const formattedChildren = children.map((child) => ({
...child,
hasChildren: fullTreeData.value.some((c) => c.sid === child.id)
}));
node.children = formattedChildren;
if (formattedChildren.length > 0) {
loadAllChildren(formattedChildren);
}
});
};
loadAllChildren(state.tableData);
// 展开所有节点
state.tableData.forEach((row) => {
tableRef.value.toggleRowExpansion(row, true);
});
ElMessage.success('所有节点已展开');
} catch (error) {
ElMessage.error(`一键展开失败:${error.message}`);
} finally {
loadingInstance.value?.close();
}
} else {
// 收起所有节点
state.tableData.forEach((row) => {
tableRef.value.toggleRowExpansion(row, false);
});
expandRowKeys.value = [];
}
};
/**
* 处理输入框变化(优化性能)
*/
const handleInputChange = (row, field) => {
// 只记录修改过的数据,避免全量对比
if (!editDataMap.value.has(row.id)) {
editDataMap.value.set(row.id, { ...row });
}
const storedData = editDataMap.value.get(row.id);
storedData[field] = row[field];
editDataMap.value.set(row.id, storedData);
};
/**
* 获取主数据列表(核心优化:拆分根节点和完整数据)
*/
async function getMasterDataList() {
try {
state.loading.list = true;
// 获取主数据
const masterDataRes = await obtainMasterDataList({
projectId: currentProject.value?.id
});
const { data: masterData } = masterDataRes;
if (!masterData[0]?.id) {
state.tableData = [];
fullTreeData.value = [];
return;
}
state.masterData = masterData[0];
// 获取完整供应计划数据
const supplyPlanRes = await totalsupplyplan({ id: masterData[0].id });
const allData = supplyPlanRes.rows || [];
// 存储完整数据到本地
fullTreeData.value = [...allData];
// 初始只加载根节点pid为空或0的节点
const rootNodes = allData.filter((item) => !item.pid || item.pid === 0);
// 标记根节点是否有子节点
state.tableData = rootNodes.map((node) => ({
...node,
hasChildren: allData.some((child) => child.pid === node.id)
}));
// 初始化可编辑数据映射
editDataMap.value.clear();
allData.forEach((item) => {
if (!item.hasChildren) {
// 只关注叶子节点
editDataMap.value.set(item.id, { ...item });
}
});
} catch (error) {
ElMessage.error(`数据加载失败:${error.message}`);
state.tableData = [];
fullTreeData.value = [];
} finally {
state.loading.list = false;
}
}
/**
* 获取详情
*/
async function totalSupplyplanDetail(id) {
try {
const result = await totalSupplyplanDetails(id);
if (result?.code === 200) {
const detailData = result.data || {};
// 清空表单并赋值
Object.keys(formData).forEach((key) => {
formData[key] = undefined;
});
// 处理日期格式
const formatDate = (date) => {
if (!date) return '';
const d = typeof date === 'string' ? new Date(date) : date;
return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`;
};
Object.assign(formData, {
...detailData,
compileDate: formatDate(detailData.compileDate),
dateService: formatDate(detailData.dateService)
});
} else {
ElMessage.error(`获取详情失败: ${result?.msg || '未知错误'}`);
}
} catch (err) {
ElMessage.error(`接口请求失败: ${err.message}`);
console.error('详情接口错误:', err);
} finally {
state.loading.list = false;
}
}
/**
* 一键保存修改
*/
const editApprovalSheet = async () => {
state.loading.list = true;
try {
// 只提交修改过的数据从Map中获取
const modifiedData = Array.from(editDataMap.value.values());
if (modifiedData.length === 0) {
ElMessage.info('没有数据需要修改');
return;
}
await totalSupplyplanBatchEdit(modifiedData);
ElMessage.success('修改成功');
getMasterDataList(); // 重新加载数据
} catch (error) {
ElMessage.error(`保存失败:${error.message}`);
} finally {
state.loading.list = false;
}
};
/**
* 提交表单
*/
const handleSubmit = async () => {
try {
await formRef.value.validate();
await materialChangeSupplyplan(formData);
ElMessage.success('修改成功');
handleClose();
getMasterDataList();
} catch (error) {
console.error('表单验证失败:', error);
}
};
/**
* 导出数据
*/
const handleExport = async () => {
proxy?.download('/design/totalsupplyplan/export', { projectId: currentProject.value?.id }, `物资供应总计划.xlsx`, true);
};
/**
* 关闭弹窗
*/
const handleClose = () => {
visible.value = false;
formRef.value?.resetFields();
Object.keys(formData).forEach((key) => {
formData[key] = undefined;
});
};
/**
* 审批
*/
function clickApprovalSheet1() {
proxy.$tab.closePage(proxy.$route);
proxy.$router.push({
path: `/approval/overallPlanMaterialSupply/indexEdit`,
query: { id: state.masterData.id, type: 'update' }
});
}
/**
* 查看流程
*/
function lookApprovalFlow() {
proxy.$router.push({
path: `/approval/overallPlanMaterialSupply/indexEdit`,
query: { id: state.masterData.id, type: 'view' }
});
}
/**
* 监听项目变化
*/
const listeningProject = watch(
() => currentProject.value?.id,
(nid) => {
if (nid) getMasterDataList();
}
);
// 导入成功处理
const handleSuccess = () => {
ElMessage.success('导入成功');
getMasterDataList();
};
// 生命周期
onMounted(() => {
if (currentProject.value?.id) {
getMasterDataList();
}
});
onUnmounted(() => {
listeningProject();
loadingInstance.value?.close();
});
</script>
<style>
.overall-plan-material-supply {
padding: 20px;
}
.space-y-4 > .el-row {
margin-bottom: 16px;
}
.space-y-4 > .el-row:last-child {
margin-bottom: 0;
}
</style>

File diff suppressed because it is too large Load Diff

View File

@ -133,7 +133,7 @@
></el-col>
<el-col :span="12" :offset="0"
><el-form-item label="供应商" prop="supplier">
<el-select v-model="form.supplierId" value-key="id" placeholder="请选择供应商" clearable filterable @change="">
<el-select v-model="form.supplierId" value-key="id" placeholder="请选择供应商" clearable filterable @change="getPlanList">
<el-option v-for="item in supplierOptions" :key="item.id" :label="item.supplierName" :value="item.id"> </el-option>
</el-select> </el-form-item
></el-col>
@ -145,7 +145,14 @@
></el-col>
<el-col :span="12" :offset="0">
<el-form-item label="需求计划" prop="planId">
<el-select v-model="form.planId" value-key="id" placeholder="请选择需求计划" multiple filterable :disabled="!form.mrpBaseId">
<el-select
v-model="form.planId"
value-key="id"
placeholder="请选择需求计划"
multiple
filterable
:disabled="!form.mrpBaseId && !form.supplierId"
>
<el-option v-for="item in planList" :key="item.id" :label="item.name" :value="item.id"> </el-option>
</el-select> </el-form-item
></el-col>
@ -190,18 +197,17 @@
></el-col> -->
</el-row>
</el-form>
<el-table v-loading="loading" :data="selectPlanList" v-if="form.id">
<el-table v-loading="loading" :data="selectPlanList">
<el-table-column label="物资名称" align="center" prop="name" />
<el-table-column label="质量标准" align="center" prop="qs" />
<el-table-column label="规格型号" align="center" prop="specification" />
<el-table-column label="计量单位" align="center" prop="unit" width="80" />
<el-table-column label="需求数量" align="center" prop="demandQuantity" v-if="form.docType == 2">
<el-table-column label="需求数量" align="center" prop="demandQuantity">
<template #default="scope">
<el-input v-model="scope.row.demandQuantity" placeholder="请输入" type="number" />
</template>
</el-table-column>
<el-table-column label="需求数量" align="center" prop="demandQuantity" v-else />
<!-- <el-table-column label="需求数量" align="center" prop="demandQuantity" v-else /> -->
<!-- <el-table-column label="需求到货时间" align="center" prop="arrivalTime" width="250" /> -->
<el-table-column label="备注" align="center" prop="remark" />
</el-table>
@ -357,6 +363,7 @@ const data = reactive({
docCode: [{ required: true, message: '采购单编号不能为空', trigger: 'blur' }],
planId: [{ required: true, message: '需求计划不能为空', trigger: 'blur' }],
mrpBaseId: [{ required: true, message: '需求批次号不能为空', trigger: 'blur' }],
supplierId: [{ required: true, message: '供应商不能为空', trigger: 'blur' }],
// 电话号码验证
technicalDirectorTel: [
{ required: true, message: '请输入电话', trigger: 'blur' },
@ -461,9 +468,16 @@ const handleUpdate = async (row?: PurchaseDocVO) => {
const _id = row?.id || ids.value[0];
const res = await getPurchaseDoc(_id);
Object.assign(form.value, res.data);
getPlanList();
form.value.planId = form.value.associationList?.map((item: any) => item.planId);
await getPlanList();
form.value.planId = form.value.associationList?.map((item: any) => item.planId);
planList.value.forEach((item) => {
form.value.associationList.forEach((items: any) => {
if (item.id == items.planId) {
item.demandQuantity = items.demandQuantity;
}
});
});
dialog.visible = true;
dialog.title = '修改物资-采购联系单';
};
@ -471,6 +485,7 @@ const handleUpdate = async (row?: PurchaseDocVO) => {
const selectPlanList = computed(() => {
if (!form.value.planId) return [];
const result = planList.value.filter((item) => form.value.planId.includes(item.id));
return result;
});
@ -479,11 +494,12 @@ const submitForm = () => {
purchaseDocFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
form.value.associationList = form.value.planId?.map((item: any) => ({
planId: item
}));
form.value.associationList = selectPlanList.value;
form.value.associationList.forEach((item: any) => {
item.planId = item.id;
delete item.id;
});
form.value.supplier = supplierOptions.value.find((item) => item.id == form.value.supplierId)?.supplierName;
if (form.value.id) {
await updatePurchaseDoc(form.value).finally(() => (buttonLoading.value = false));
} else {
@ -498,13 +514,16 @@ const submitForm = () => {
const getPlanList = async () => {
form.value.planId = '';
const res = await getBatch({
pageNum: 1,
pageSize: 10,
projectId: currentProject.value?.id,
mrpBaseId: form.value.mrpBaseId
});
planList.value = res.rows;
if (form.value.mrpBaseId && form.value.supplierId) {
const res = await getBatch({
pageNum: 1,
pageSize: 10,
projectId: currentProject.value?.id,
mrpBaseId: form.value.mrpBaseId,
supplierId: form.value.supplierId
});
planList.value = res.rows;
}
};
const getBatchList = async () => {

View File

@ -51,9 +51,10 @@
size="small"
icon="Plus"
type="primary"
v-if="scope.row.purchaseSubmission == '0'"
link
@click="handleAddSon(scope.row)"
>添加</el-button
>提交</el-button
>
<el-button type="primary" v-hasPermi="['cailiaoshebei:physicalsupply:edit']" size="small" icon="Edit" link @click="handleEdit(scope.row)"
>修改</el-button
@ -113,7 +114,7 @@
</el-col>
<el-col :span="12">
<el-form-item label="运算周期(天)" prop="executionCycle">
<el-input v-model.number="formData.executionCycle" placeholder="请输入运算周期" type="number"></el-input>
<el-input v-model.number="formData.executionCycle" min="0" placeholder="请输入运算周期" type="number"></el-input>
</el-form-item>
</el-col>
</el-row>
@ -121,12 +122,12 @@
<el-row :gutter="20">
<el-col :span="12">
<el-form-item label="安装量" prop="installationQuantity">
<el-input v-model="formData.installationQuantity" placeholder="请输入安装量"></el-input>
<el-input v-model="formData.installationQuantity" min="0" type="number" placeholder="请输入安装量"></el-input>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="安装比例" prop="installationRatio">
<el-input v-model="formData.installationRatio" placeholder="请输入安装比例" suffix="%"></el-input>
<el-input v-model="formData.installationRatio" min="0" type="number" placeholder="请输入安装比例" suffix="%"></el-input>
</el-form-item>
</el-col>
</el-row>
@ -296,22 +297,7 @@ const handleSearch = () => {
// 刷新数据
const refreshData = () => {
fetchData();
ElMessage.success('数据已刷新');
};
// 分页大小改变
const handleSizeChange = (val) => {
pageSize.value = val;
currentPage.value = 1;
fetchData();
};
// 当前页改变
const handleCurrentChange = (val) => {
currentPage.value = val;
fetchData();
};
// 新增
const handleAdd = () => {
dialogType.value = 'add';
@ -476,6 +462,17 @@ const jumpRouter = (row) => {
}
});
};
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value?.id,
(nid, oid) => {
fetchData();
}
);
onUnmounted(() => {
listeningProject();
});
// 初始化页面
onMounted(() => {
fetchData();

View File

@ -30,9 +30,7 @@
:row-class-name="tableRowClassName"
>
<!-- 基础信息列 -->
<el-table-column prop="id" label="ID" width="180" align="center"></el-table-column>
<el-table-column prop="batch" label="批次" align="center"></el-table-column>
<el-table-column prop="physicalsupplyId" label="使用情况ID" width="180" align="center"></el-table-column>
<!-- 时间相关列 -->
<el-table-column prop="issuanceTime" label="联系单下达时间" min-width="160" align="center">
<template #default="scope">

View File

@ -14,6 +14,7 @@
</el-form-item>
<el-form-item label="类型" prop="type">
<el-select v-model="queryParams.type" placeholder="请选择类型">
<el-option label="全部" value="0" />
<el-option label="对甲" value="1" />
<el-option label="对乙" value="2" />
</el-select>
@ -36,7 +37,6 @@
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="monthPlanList">
<el-table-column type="index" label="序号" width="55" align="center" />
<el-table-column label="计划月份" align="center" prop="planMonth" />
@ -82,7 +82,7 @@
<el-form-item label="计划产值(元)" prop="planValue">
<el-input v-model="form.planValue" placeholder="请输入计划产值" type="number" />
</el-form-item>
<el-form-item label="计划月份(元)" prop="planMonth">
<el-form-item label="计划月份" prop="planMonth">
<el-date-picker v-model="form.planMonth" type="month" value-format="YYYY-MM" placeholder="请选择计划月份" />
</el-form-item>
<el-form-item label="产值类型" prop="valueType">
@ -103,7 +103,7 @@
<script setup name="MonthPlan" lang="ts">
import { listMonthPlan, getMonthPlan, delMonthPlan, addMonthPlan, updateMonthPlan } from '@/api/out/monthPlan';
import { MonthPlanVO, MonthPlanQuery, MonthPlanForm } from '@/api/out/monthPlan/types';
import { MonthPlanVO } from '@/api/out/monthPlan/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { out_value_type } = toRefs<any>(proxy?.useDict('out_value_type'));
const { wf_business_status } = toRefs<any>(proxy?.useDict('wf_business_status'));
@ -113,13 +113,7 @@ import { useUserStoreHook } from '@/store/modules/user';
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const month = computed(() => {
const now = new Date();
const year = now.getFullYear();
const month = String(now.getMonth() + 1).padStart(2, '0'); // getMonth() 从0开始
const currentMonth = `${year}-${month}`;
return currentMonth;
});
const month = '';
const monthPlanList = ref<MonthPlanVO[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
@ -164,7 +158,7 @@ const data = reactive({
valueType: undefined,
planAuditStatus: undefined,
completeAuditStatus: undefined,
type: '1',
type: '0',
params: {}
},
rules: {
@ -181,7 +175,11 @@ const { queryParams, form, rules } = toRefs(data);
/** 查询月度产值计划列表 */
const getList = async () => {
loading.value = true;
const res = await listMonthPlan(queryParams.value);
let type = queryParams.value.type;
if (type == '0') {
type = '';
}
const res = await listMonthPlan({ ...queryParams.value, type: type });
monthPlanList.value = res.rows;
total.value = res.total;
loading.value = false;
@ -242,7 +240,6 @@ const submitForm = () => {
if (valid) {
buttonLoading.value = true;
form.value.isDesign = true;
if (form.value.id) {
await updateMonthPlan(form.value).finally(() => (buttonLoading.value = false));
} else {
@ -266,8 +263,10 @@ const handleDelete = async (row?: MonthPlanVO) => {
/** 审核按钮操作 */
const handleAudit = async (row?: MonthPlanVO) => {
proxy.$tab.closePage(proxy.$route);
proxy?.$tab.openPage('/approval/monthPlan/indexEdit', '审核月度产值计划', {
planMonth: row?.planMonth,
id: row?.id,
type: 'update'
});
};

View File

@ -104,6 +104,7 @@ import { StartProcessBo } from '@/api/workflow/workflowCommon/types';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
import { useUserStoreHook } from '@/store/modules/user';
import { getMonthInfo, isSubmit } from '@/api/out/monthPlan';
import { id } from 'element-plus/es/locale/index.mjs';
// 获取用户 store
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
@ -169,14 +170,12 @@ const getInfo = () => {
loading.value = true;
buttonLoading.value = false;
nextTick(async () => {
console.log('🚀 ~ routeParams.value:', routeParams.value.businessId);
const projectId = routeParams.value.businessId ? routeParams.value.businessId.split('_')[0] : currentProject.value?.id;
const res = await getMonthInfo({ projectId: projectId, planMonth: routeParams.value.planMonth });
console.log(routeParams.value);
// routeParams.value.businessId ? routeParams.value.businessId.split('_')[0] : routeParams.value.id;
const res = await getMonthInfo({ id: routeParams.value.id.split('_')[0] });
form.value = res.data as any;
console.log('🚀 ~ getInfo ~ form.value:', form.value[0].projectId);
form.value[0].id = form.value[0].projectId + '_' + form.value[0].planMonth;
form.value[0].mid = form.value[0].id;
form.value[0].id = form.value[0].id + '_plan';
loading.value = false;
buttonLoading.value = false;
});
@ -196,7 +195,7 @@ const submitFlow = async () => {
const handleStartWorkFlow = async (data: LeaveForm) => {
try {
submitFormData.value.flowCode = flowCode.value;
submitFormData.value.businessId = currentProject.value?.id + '_' + form.value[0]?.planMonth;
submitFormData.value.businessId = routeParams.value.id + '_plan';
//流程变量
taskVariables.value = {
@ -215,8 +214,6 @@ const handleStartWorkFlow = async (data: LeaveForm) => {
};
//审批记录
const handleApprovalRecord = () => {
console.log(form.value[0]?.id);
approvalRecordRef.value.init(form.value[0]?.id);
};
//提交回调
@ -237,12 +234,6 @@ const submit = async (status, data) => {
proxy.$tab.closePage(proxy.$route);
proxy.$router.go(-1);
} else {
const res = await isSubmit(data[1]?.id);
if (!res.data) {
proxy?.$modal.msgError('三种计划产值必须填写');
return;
}
if ((form.value[0]?.planAuditStatus === 'draft' && (flowCode.value === '' || flowCode.value === null)) || routeParams.value.type === 'add') {
flowCode.value = flowCodeOptions[0].value;
dialogVisible.visible = true;
@ -259,12 +250,9 @@ const submit = async (status, data) => {
onMounted(() => {
nextTick(async () => {
routeParams.value = proxy.$route.query;
console.log('🚀 ~ proxy.$route.query:', proxy.$route.query);
reset();
loading.value = false;
if (routeParams.value.type === 'update' || routeParams.value.type === 'view' || routeParams.value.type === 'approval') {
console.log('🚀 ~ routeParams.value:', routeParams.value);
getInfo();
}
});

View File

@ -86,13 +86,14 @@ const handleTabChange = (tab) => {
activeTab.value = tab;
data.queryParams.valueType = '1';
// data.queryParams.month = '';
getList();
};
/** 查询项目总产值分配列表 */
const getList = async () => {
loading.value = true;
const res = await listOutTable(queryParams.value);
const res = await listOutTable({ ...queryParams.value, type: activeTab.value });
valueAllocationList.value = res.rows;
total.value = res.total;
loading.value = false;

View File

@ -85,7 +85,7 @@ const handleQuery = () => {
/** 重置按钮操作 */
const resetQuery = () => {
queryParams.value.month = '';
resetMonth();
handleQuery();
};
// 获取列表
@ -102,14 +102,17 @@ const getList = async () => {
total.value = res.total;
}
};
onMounted(() => {
const currentDate = new Date();
const resetMonth=()=>{
const currentDate = new Date();
const year = currentDate.getFullYear();
const month = currentDate.getMonth() + 1; // 月份从0开始所以需要加1
// 形成"YYYY-M"格式
const formattedDate = `${year}-${String(month).padStart(2, '0')}`;
queryParams.value.month = formattedDate;
}
onMounted(() => {
resetMonth();
getList();
});

View File

@ -120,17 +120,17 @@
<!-- <el-input v-model="form.projectStructure" placeholder="请输入对应项目结构" /> -->
</el-form-item>
<el-form-item label="预计开始时间" prop="planStartDate">
<el-date-picker clearable v-model="form.planStartDate" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择预计开始时间" />
<el-date-picker clearable v-model="form.planStartDate" type="date" value-format="YYYY-MM-DD" placeholder="选择预计开始时间" />
</el-form-item>
<el-form-item label="预计结束时间" prop="planEndDate">
<el-date-picker clearable v-model="form.planEndDate" type="datetime" value-format="YYYY-MM-DD HH:mm:ss" placeholder="选择预计结束时间" />
<el-date-picker clearable v-model="form.planEndDate" type="date" value-format="YYYY-MM-DD" placeholder="选择预计结束时间" />
</el-form-item>
<el-form-item label="实际开始时间" prop="practicalStartDate">
<el-date-picker
clearable
v-model="form.practicalStartDate"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
type="date"
value-format="YYYY-MM-DD"
placeholder="选择实际开始时间"
/>
</el-form-item>
@ -138,8 +138,8 @@
<el-date-picker
clearable
v-model="form.practicalEndDate"
type="datetime"
value-format="YYYY-MM-DD HH:mm:ss"
type="date"
value-format="YYYY-MM-DD"
placeholder="选择实际结束时间"
/>
</el-form-item>

View File

@ -161,7 +161,7 @@
:total="detailTotalWork"
v-model:page="detailQueryParamsWork.pageNum"
v-model:limit="detailQueryParamsWork.pageSize"
@pagination="getPvModuleList"
@pagination="getDailyBookList"
layout="total, sizes, prev, pager, next"
:isSmall="5"
/>
@ -436,10 +436,10 @@ const handleView = (row: any, obj: any) => {
// 获取已填日报
const getDailyBookList = (doneTime: string) => {
detialWordList.value = [];
// detialWordList.value = [];
getDailyBook({
id: formDetail.value.id,
...detailQueryParams.value
...detailQueryParamsWork.value
}).then((res: any) => {
if (res.code === 200) {
detialWordList.value = res.rows || [];

View File

@ -298,6 +298,11 @@ const getList = async () => {
const res = await getProjectSquare(currentProject.value?.id);
if (res.data.length === 0) {
proxy?.$modal.msgWarning('当前项目下没有方阵,请先创建方阵');
matrixOptions.value = [];
queryParams.value.projectId = '';
form.value.projectId = '';
tabList.value = [];
activeTab.value = '';
} else {
let matrixList = res.data.map((item) => {
return {
@ -472,6 +477,7 @@ const matrixValue = ref<number | undefined>(matrixOptions.value.length > 0 ? mat
const resetMatrix = () => {
matrixValue.value = undefined;
queryParams.value.matrixId = undefined;
queryParams.value.projectId = undefined;
matrixOptions.value = [];
};
@ -491,8 +497,6 @@ onMounted(() => {
const listeningProject = watch(
() => currentProject.value?.id,
(nid, oid) => {
queryParams.value.projectId = nid;
form.value.projectId = nid;
resetMatrix();
getList();
}

View File

@ -216,7 +216,7 @@ const { queryParams, form, rules } = toRefs(data);
/** 查询进度类别模版列表 */
const getList = async () => {
loading.value = true;
const res = await listProgressCategoryTemplateByParent(activeTab.value);
const res = await listProgressCategoryTemplateByParent(activeTab.value,queryParams.value.name);
const data = proxy?.handleTree<ProgressCategoryTemplateVO>(res.data, 'id', 'parentId');
if (data) {
progressCategoryTemplateList.value = data;

View File

@ -44,11 +44,11 @@
{{ userDetail?.sfzNumber }}
</el-form-item>
</el-col>
<el-col :span="12">
<!-- <el-col :span="12">
<el-form-item label="身份证号码">
{{ userDetail?.sfzNumber }}
</el-form-item>
</el-col>
</el-col> -->
<el-col :span="12">
<el-form-item label="身份证有效开始期">
{{ dayjs(userDetail?.sfzStart).format('YYYY 年 MM 月 DD 日') }}
@ -59,7 +59,7 @@
{{ dayjs(userDetail?.sfzEnd).format('YYYY 年 MM 月 DD 日') }}
</el-form-item>
</el-col>
<el-col :span="12">
<el-col :span="24">
<el-form-item label="身份证地址">
{{ userDetail?.sfzSite }}
</el-form-item>

View File

@ -30,10 +30,17 @@
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['land:enterRoad:add']">新增</el-button>
</el-col>
<!-- <el-col :span="1.5">
<el-button type="success" plain icon="Plus" @click="downloadTemplate" v-hasPermi="['land:enterRoad:import']">模板下载</el-button>
</el-col> -->
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['land:enterRoad:remove']"
>删除</el-button
>
<el-upload
ref="uploadRef"
v-hasPermi="['land:enterRoad:upload']"
class="upload-demo"
:http-request="handleImport"
:show-file-list="false"
/>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>

View File

@ -10,7 +10,7 @@
<el-form-item label="地块名称" prop="landName">
<el-input v-model="queryParams.landName" placeholder="请输入地块名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="所属村委会" prop="villageCommittee">
<!-- <el-form-item label="所属村委会" prop="villageCommittee">
<el-input v-model="queryParams.villageCommittee" placeholder="请输入所属村委会" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="设计面积" prop="designArea">
@ -19,9 +19,9 @@
<el-form-item label="地块数" prop="blockCount">
<el-input v-model="queryParams.blockCount" placeholder="请输入地块数" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="农户数" prop="farmerCount">
<el-input v-model="queryParams.farmerCount" placeholder="请输入农户数" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="农户数(户)" prop="farmerCount">
<el-input v-model="queryParams.farmerCount" type="number" placeholder="请输入农户数" clearable @keyup.enter="handleQuery" />
</el-form-item> -->
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
@ -36,10 +36,18 @@
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['land:landBlock:add']">新增</el-button>
</el-col>
<!-- <el-col :span="1.5">
<el-button type="success" plain icon="Plus" @click="downloadTemplate" v-hasPermi="['land:enterRoad:import']">模板下载</el-button>
</el-col> -->
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['land:landBlock:remove']"
>删除</el-button
>
<el-upload ref="uploadRef" class="upload-demo" :http-request="handleImport" :show-file-list="false">
<template #trigger>
<el-button plain type="primary" icon="upload">导入excel</el-button>
</template>
</el-upload>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Download" @click="exportFile">导出模版</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
@ -70,6 +78,8 @@
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 地块表单弹窗 -->
<el-dialog draggable :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="landBlockFormRef" :model="form" :rules="rules" label-width="100px">
<el-form-item label="地块编号" prop="landCode">
@ -101,12 +111,24 @@
</div>
</template>
</el-dialog>
<el-dialog draggable :title="dialogMatrix.title" v-model="dialogMatrix.visible" width="800px" append-to-body>
<!-- 关联方阵弹窗核心修复区域 -->
<el-dialog draggable :title="dialogMatrix.title" v-model="dialogMatrix.visible" width="900px" append-to-body>
<el-button type="primary" plain icon="Plus" @click="addUnitBoItem" style="margin-bottom: 15px">添加</el-button>
<el-form ref="landBlockFormMatrixRef" :model="formM" :rules="rules" label-width="100px">
<el-row v-for="(item, i) of unitBoList" :key="i">
<el-col :span="9">
<el-form-item label="方阵" prop="matrixId">
<!-- 修复1表单模型绑定formM根模型确保嵌套字段校验生效 -->
<el-form ref="landBlockFormMatrixRef" :model="formM" label-width="100px">
<!-- 修复2循环formM.unitBoList而非独立ref保证数据双向绑定 -->
<el-row v-for="(item, i) of formM.unitBoList" :key="i" class="mb-4">
<!-- 方阵选择修复校验规则移除min:2改为min:1适配单层级选择 -->
<el-col :span="8">
<el-form-item
label="方阵"
:prop="`unitBoList[${i}].unitProjectId`"
:rules="[
{ required: true, message: '请选择方阵', trigger: 'change' },
{ type: 'array', min: 1, message: '请选择完整的方阵', trigger: 'change' }
]"
>
<el-cascader
:options="fangzhenList"
placeholder="请选择"
@ -117,18 +139,31 @@
/>
</el-form-item>
</el-col>
<el-col :span="7">
<el-form-item label="所属工区" prop="unitProjectArea">
<!-- 所属工区保留原有规则 -->
<el-col :span="8">
<el-form-item
label="所属工区"
:prop="`unitBoList[${i}].unitProjectArea`"
:rules="{ required: true, message: '请输入所属工区', trigger: 'blur' }"
>
<el-input v-model="item.unitProjectArea" placeholder="请输入所属工区" />
</el-form-item>
</el-col>
<!-- 方阵状态保留原有规则 -->
<el-col :span="6">
<el-form-item label="方阵状态" prop="unitProjectStatus">
<el-input v-model="item.unitProjectStatus" placeholder="请输入方阵状态" />
</el-form-item>
</el-col>
<el-col :span="1" style="margin-left: 15px">
<el-button type="danger" icon="Delete" @click="removeUnitBoItem(i)"></el-button>
<!-- 删除按钮禁用逻辑优化至少保留1项 -->
<el-col :span="1" style="margin-left: 15px; display: flex; align-items: flex-end">
<el-button
style="margin-bottom: 18px"
type="danger"
icon="Delete"
@click="removeUnitBoItem(i)"
:disabled="formM.unitBoList.length <= 1"
></el-button>
</el-col>
</el-row>
</el-form>
@ -146,23 +181,55 @@
import { listLandBlock, getLandBlock, delLandBlock, LandUnit, addLandBlock, updateLandBlock, subMatrix } from '@/api/system/landTransfer/landBlock';
import { LandBlockVO, LandBlockQuery, LandBlockForm } from '@/api/system/landTransfer/landBlock/types';
import { useUserStoreHook } from '@/store/modules/user';
import { getCurrentInstance, ComponentInternalInstance, onMounted, onUnmounted, watch, reactive, ref, toRefs, computed } from 'vue';
import { ElFormInstance } from 'element-plus';
// 类型定义补充
interface DialogOption {
visible: boolean;
title: string;
}
// 方阵级联选择器选项类型
interface FangzhenOption {
matrixId: string | number;
name: string;
children?: FangzhenOption[];
}
// 方阵表单项类型
interface UnitBoItem {
unitProjectArea: string;
unitProjectStatus: string;
unitProjectId: (string | number)[]; // 级联选择值(数组)
}
// 方阵表单根模型类型关键显式声明unitBoList
interface MatrixForm {
landId?: string | number; // 关联的地块ID
unitBoList: UnitBoItem[]; // 方阵列表
}
// 基础实例与Store
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
// 获取用户 store
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const landBlockList = ref<LandBlockVO[]>([]);
const fangzhenList = ref([]); //方阵列表数据
const fangzhenList = ref<FangzhenOption[]>([]);
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const unitBoList = ref([
{
unitProjectArea: '1',
unitProjectStatus: '1',
unitProjectId: []
}
]);
const uploadRef = ref<any>(null);
// 方阵表单模型核心修复使用reactive并显式声明结构
const formM = ref<MatrixForm>({
landId: undefined,
unitBoList: [{ unitProjectArea: '', unitProjectStatus: '', unitProjectId: [] }]
});
// 表格选择相关
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
@ -194,9 +261,6 @@ const initFormData: LandBlockForm = {
};
const data = reactive({
form: { ...initFormData },
formM: {
landId: undefined
},
queryParams: {
pageNum: 1,
pageSize: 10,
@ -209,6 +273,7 @@ const data = reactive({
farmerCount: undefined,
params: {}
},
// 地块表单规则
rules: {
id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }],
projectId: [{ required: true, message: '项目ID不能为空', trigger: 'blur' }],
@ -217,7 +282,7 @@ const data = reactive({
}
});
const { queryParams, form, rules, formM } = toRefs(data);
const { queryParams, form, rules } = toRefs(data);
/** 查询地块信息列表 */
const getList = async () => {
@ -305,50 +370,78 @@ const handleDelete = async (row?: LandBlockVO) => {
// 获取方阵列表
const getfangzhenList = async () => {
loading.value = true;
const res = await subMatrix(currentProject.value.id);
res.data.forEach((item) => {
item.children.forEach((item2) => {
item2.matrixId = item2.name + '_' + item2.matrixId;
try {
const res = await subMatrix(currentProject.value.id);
// 处理方阵数据(级联选择需父子结构)
res.data.forEach((item: any) => {
item.children?.forEach((item2: any) => {
item2.matrixId = `${item2.name}_${item2.matrixId}`;
});
});
});
fangzhenList.value = res.data;
fangzhenList.value = res.data;
} catch (err) {
proxy?.$modal.msgError('获取方阵列表失败');
} finally {
loading.value = false;
}
};
const handleView = async (row) => {
// 关联方阵
/** 关联方阵 */
const handleView = async (row: LandBlockVO) => {
if (!row?.id) return proxy?.$modal.msgWarning('请选择有效的地块');
console.log('🚀 ~ handleView ~ row:', row);
// 重置方阵表单
resetMatrix();
// 绑定当前地块ID
formM.value.landId = row.id;
// 打开弹窗
dialogMatrix.visible = true;
dialogMatrix.title = '关联方阵';
data.formM.landId = row.id;
dialogMatrix.title = `关联方阵(地块:${row.landName || row.landCode}`;
};
// 动态添加unitBoList项
const addUnitBoItem = () => {
unitBoList.value.push({
formM.value.unitBoList.push({
unitProjectArea: '',
unitProjectStatus: '',
unitProjectId: []
});
// 重置校验状态
landBlockFormMatrixRef.value?.clearValidate();
};
// 移除unitBoList项
const removeUnitBoItem = (index: number) => {
if (unitBoList.value.length > 1) {
unitBoList.value.splice(index, 1);
} else {
proxy?.$modal.msgWarning('至少保留一项');
if (formM.value.unitBoList.length <= 1) {
return proxy?.$modal.msgWarning('至少保留一项方阵配置');
}
formM.value.unitBoList.splice(index, 1);
landBlockFormMatrixRef.value?.clearValidate();
};
/** 提交方阵关联表单(核心修复:数据处理逻辑) */
const submitFormMatrix = () => {
landBlockFormMatrixRef.value?.validate(async (valid: boolean) => {
if (valid) {
var arr = [];
unitBoList.value.forEach((item) => {
let str = item.unitProjectId[1].split('_');
arr.push({
unitProjectArea: item.unitProjectArea,
unitProjectStatus: item.unitProjectStatus,
unitProjectId: str[1],
unitProjectName: str[0]
});
if (!valid) return;
if (!formM.value.landId) return proxy?.$modal.msgWarning('地块ID异常请重新选择地块');
try {
// 处理方阵数据修复ID拆分逻辑
const unitBoListParams = formM.value.unitBoList.map((item) => {
// 取级联选择的最后一层值
const lastLevelValue = item.unitProjectId[item.unitProjectId.length - 1];
if (!lastLevelValue) throw new Error('请选择完整的方阵');
// 拆分名称和ID
const [unitProjectName, unitProjectId] = String(lastLevelValue).split('_');
if (!unitProjectId) throw new Error('方阵ID解析失败请重新选择');
return {
unitProjectArea: item.unitProjectArea.trim(),
unitProjectStatus: item.unitProjectStatus.trim(),
unitProjectId: unitProjectId,
unitProjectName: unitProjectName
};
});
var res = await LandUnit({ ...formM.value, unitBoList: arr });
if (res.code == 200) {
@ -366,28 +459,87 @@ const cancelMatrix = () => {
resetMatrix();
dialogMatrix.visible = false;
};
/** 表单重置 */
/** 方阵表单重置 */
const resetMatrix = () => {
data.formM.landId = '';
unitBoList.value = [
{
unitProjectArea: '1',
unitProjectStatus: '1',
unitProjectId: []
}
];
landBlockFormMatrixRef.value?.resetFields();
if (landBlockFormMatrixRef.value) {
landBlockFormMatrixRef.value.resetFields();
landBlockFormMatrixRef.value.clearValidate();
}
formM.value.landId = undefined;
formM.value.unitBoList = [{ unitProjectArea: '', unitProjectStatus: '', unitProjectId: [] }];
};
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value.id,
(nid, oid) => {
queryParams.value.projectId = nid;
getfangzhenList();
getList();
}
() => currentProject.value?.id,
(newId) => {
if (newId) {
queryParams.value.projectId = newId;
getfangzhenList();
getList();
}
},
{ immediate: true }
);
/** 导入Excel */
const handleImport = (options: { file: File }) => {
if (!currentProject.value?.id) return proxy?.$modal.msgWarning('请先选择项目');
if (!options.file) return proxy?.$modal.msgWarning('请选择Excel文件');
loading.value = true;
const formData = new FormData();
formData.append('file', options.file);
importLandBlock(currentProject.value.id, formData)
.then((res) => {
if (res.code === 200) {
proxy?.$modal.msgSuccess(res.msg || '导入成功');
getList();
getfangzhenList();
} else {
proxy?.$modal.msgError(res.msg || '导入失败');
}
})
.catch((err) => {
proxy?.$modal.msgError(err.msg || '导入接口异常');
})
.finally(() => {
loading.value = false;
});
};
/** 导出模板 */
const exportFile = () => {
try {
const link = document.createElement('a');
link.href = '/dikuai.xlsx';
link.download = '地块信息导入模版.xlsx';
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
} catch (error) {
alert('下载失败,请重试');
}
};
/** 下载导入模板 */
const downloadTemplate = () => {
try {
const link = document.createElement('a');
link.href = '/landBlock.xlsx';
link.download = '地块信息导入模板.xlsx';
document.body.appendChild(link);
link.click();
} catch (err) {
proxy?.$modal.msgError('模板下载失败,请重试');
} finally {
const link = document.querySelector('a[download="地块信息导入模板.xlsx"]');
if (link) document.body.removeChild(link);
}
};
/** 生命周期:组件卸载时清理监听 */
onUnmounted(() => {
listeningProject();
});

View File

@ -135,12 +135,16 @@
<!-- <el-table-column label="备注" align="center" prop="remark" /> -->
<el-table-column label="创建时间" align="center" prop="createTime" width="180" />
<!-- <el-table-column fixed="right" label="操作" align="center" class-name="small-padding fixed-width" width="400">
<el-table-column fixed="right" label="操作" align="center" width="500">
<template #default="scope">
<el-space>
<el-button link type="primary" icon="Edit" @click="handleCheckRules(scope.row)" v-hasPermi="['project:attendanceRule:byProjectId']"
>打卡规则
</el-button>
<el-button link type="primary" icon="Edit" @click="handleScope(scope.row)" v-hasPermi="['project:project:edit']">打卡范围 </el-button>
<el-button link type="primary" icon="FolderOpened" @click="handleShowUpload(scope.row)" v-hasPermi="['project:project:edit']"
>导入安全协议书
</el-button>
<el-button link type="success" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['project:project:edit']">修改 </el-button>
<el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['project:project:remove']">删除 </el-button>
<el-button link type="primary" icon="upload" @click="handleUpload(scope.row)" v-hasPermi="['project:project:saveTenderFile']"
@ -150,7 +154,6 @@
</template>
</el-table-column> -->
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改项目对话框 -->
@ -243,78 +246,6 @@
</el-col>
</el-row>
</div>
<div class="block-box">
<div class="">打卡设置</div>
<el-row :gutter="20">
<el-col :span="12" :offset="0">
<el-form-item label="打卡开始时间" prop="playCardStart" label-width="110px">
<!-- <el-time-picker value-format="HH:mm" v-model="form.playCardStart" placeholder="请输入打卡开始时间" /> -->
<el-time-select
v-model="form.playCardStart"
style="width: 100%"
class="mr-4"
placeholder="请输入打卡开始时间"
value-format="HH:mm"
start="00:00"
step="00:15"
end="23:59"
/>
</el-form-item>
</el-col>
<el-col :span="12" :offset="0">
<el-form-item label="打卡结束时间" prop="playCardEnd" label-width="110px">
<!-- <el-time-picker value-format="HH:mm" v-model="form.playCardEnd" placeholder="请输入打卡结束时间" /> -->
<el-time-select
v-model="form.playCardEnd"
style="width: 100%"
:min-time="form.playCardStart"
class="mr-4"
placeholder="请输入打卡结束时间"
value-format="HH:mm"
start="00:00"
step="00:15"
end="23:59"
/>
</el-form-item>
</el-col>
<el-col :span="12" :offset="0">
<el-form-item label="打卡类型" prop="playCardStart" label-width="110px">
<!-- <el-time-picker value-format="HH:mm" v-model="form.playCardStart" placeholder="请输入打卡开始时间" /> -->
<el-time-select
v-model="form.playCardStart"
style="width: 100%"
class="mr-4"
placeholder="请输入打卡开始时间"
value-format="HH:mm"
start="00:00"
step="00:15"
end="23:59"
/>
</el-form-item>
</el-col>
<el-col :span="12" :offset="0">
<el-form-item label="工作日" prop="playCardEnd" label-width="110px">
<!-- <el-time-picker value-format="HH:mm" v-model="form.playCardEnd" placeholder="请输入打卡结束时间" /> -->
<el-time-select
v-model="form.playCardEnd"
style="width: 100%"
:min-time="form.playCardStart"
class="mr-4"
placeholder="请输入打卡结束时间"
value-format="HH:mm"
start="00:00"
step="00:15"
end="23:59"
/>
</el-form-item>
</el-col>
<!-- <el-col :span="24" :offset="0">
<el-form-item label="安全协议书" prop="securityAgreement">
<file-upload v-model="form.securityAgreement" :limit="1" :file-type="['pdf']" :file-size="50" />
</el-form-item>
</el-col> -->
</el-row>
</div>
</el-form>
<template #footer>
<div class="dialog-footer">
@ -332,13 +263,12 @@
</div>
</template>
</el-dialog>
<!-- //选取项目地址弹窗 -->
<el-dialog v-model="amapStatus" :title="form.projectName + '-获取经纬度'" width="80%">
<el-dialog draggable v-model="amapStatus" :title="form.projectName + '-获取经纬度'" width="80%">
<amap height="620px" @setLocation="setPoi"></amap>
</el-dialog>
<!-- 选取方阵地址 -->
<el-dialog title="设置方阵" v-model="polygonStatus" width="1400px" :close-on-click-modal="false">
<el-dialog draggable title="设置方阵" v-model="polygonStatus" width="1400px" :close-on-click-modal="false">
<open-layers-map
:project-id="projectId"
:design-id="designId"
@ -346,7 +276,7 @@
@close="polygonStatus = false"
></open-layers-map>
</el-dialog>
<el-dialog title="添加子项目" v-model="childProjectStatus" width="400">
<el-dialog draggable title="添加子项目" v-model="childProjectStatus" width="400">
<span>填写子项目名称</span>
<el-input v-model="childProjectForm.projectName"></el-input>
<template #footer>
@ -356,7 +286,7 @@
</span>
</template>
</el-dialog>
<el-dialog title="上传文件" v-model="fileVisble" width="400">
<el-dialog draggable title="上传文件" v-model="fileVisble" width="400">
<file-upload v-model="fileForm.tenderFiles" :limit="10" :file-type="['pdf']" :file-size="50" />
<template #footer>
<div class="dialog-footer">
@ -372,14 +302,17 @@
import {
addChildProject,
addProject,
addProjectFacilities,
addProjectPilePoint,
addProjectSquare,
delProject,
uploadProjectFile,
getProject,
listProject,
updateProject
updateProject,
attendanceRuleAdd,
attendanceRuleEdit,
byProjectIdDetail,
getAttendanceRangeList,
updateAttendanceRange,
delAttendanceRange
} from '@/api/project/project';
import { ProjectForm, ProjectQuery, ProjectVO, childProjectQuery, locationType } from '@/api/project/project/types';
import amap from '@/components/amap/index.vue';
@ -422,7 +355,7 @@ const fileForm = ref({
const jsonData = ref(null);
const fullscreenLoading = ref(false);
const initFormData: ProjectForm = {
const initFormData = {
id: undefined,
projectName: undefined,
shortName: undefined,
@ -441,13 +374,15 @@ const initFormData: ProjectForm = {
lat: undefined,
plan: undefined,
onStreamTime: undefined,
playCardStart: undefined,
playCardEnd: undefined,
clockInTime: undefined,
clockOutTime: undefined,
designTotal: undefined,
securityAgreement: undefined,
sort: 0,
showHidden: undefined,
isDelete: undefined
isDelete: undefined,
type: '1',
weekday: []
};
const data = reactive<PageData<ProjectForm, ProjectQuery>>({
form: { ...initFormData },
@ -470,8 +405,6 @@ const data = reactive<PageData<ProjectForm, ProjectQuery>>({
lat: undefined,
plan: undefined,
onStreamTime: undefined,
playCardStart: undefined,
playCardEnd: undefined,
designTotal: undefined,
securityAgreement: undefined,
sort: undefined,
@ -480,8 +413,8 @@ const data = reactive<PageData<ProjectForm, ProjectQuery>>({
params: {}
},
rules: {
playCardStart: [{ required: true, message: '打卡开始时间不能为空', trigger: 'blur' }],
playCardEnd: [{ required: true, message: '打卡结束时间不能为空', trigger: 'blur' }],
clockInTime: [{ required: true, message: '打卡开始时间不能为空', trigger: 'blur' }],
clockOutTime: [{ required: true, message: '打卡结束时间不能为空', trigger: 'blur' }],
projectName: [{ required: true, message: '项目名称不能为空', trigger: 'blur' }],
shortName: [{ required: true, message: '项目简称不能为空', trigger: 'blur' }],
principalPhone: [{ required: true, message: '负责人电话不能为空', trigger: 'blur' }],

View File

@ -0,0 +1,305 @@
<template>
<el-dialog v-model="visible" title="地球可视化" :width="dialogWidth" :height="dialogHeight" :before-close="handleClose"
destroy-on-close>
<div v-loading="loading" id="earthMap" class="earth-container"></div>
<template #footer>
<el-button type="warning" @click="redraw">重新绘制</el-button>
<el-button v-if="isDraw" type="success" @click="drawRange">绘制</el-button>
<el-button type="primary" @click="handlesubmit">确定</el-button>
<el-button type="danger" @click="handleClose">关闭</el-button>
</template>
</el-dialog>
</template>
<script setup>
import { ref, onMounted, watch, defineEmits, defineProps,onUnmounted } from 'vue';
import { ElMessage } from 'element-plus';
import {
addAttendanceRange,
updateAttendanceRange,
} from '@/api/project/project';
const emit = defineEmits(['send-data', 'close']);
const props = defineProps({
position: {
type: String,
default: ''
},
data: {
type: Object,
default: () => { }
}
});
// 弹窗控制变量
const visible = ref(false);
const dialogWidth = ref('90%');
const dialogHeight = ref('90%');
let earthInstance = null;
let earthContainer = ref(null);
let loading = ref(true);
let positions = ref([]);
const entityObject = ref(null);
const isDraw = ref(true);
// 显示弹窗
const show = () => {
visible.value = true;
console.log(props.data);
if (props.data?.id) {
console.log(231, props.data);
isDraw.value = false;
}
};
// 关闭弹窗
const handleClose = () => {
visible.value = false;
// 清除地球实例
if (earthInstance && earthInstance.destroy) {
earthInstance.destroy();
earthInstance = null;
}
// 清除全局变量引用
if (window.Earth2) {
delete window.Earth2;
}
};
// 监听弹窗显示状态,初始化地球
watch(
() => visible.value,
(newVal) => {
if (newVal && earthContainer.value) {
createEarth();
}
}
);
// 创建地球
const createEarth = () => {
if (!window.YJ) {
ElMessage.error('YJ库未加载请检查依赖');
return;
}
window.YJ.on({
ws: true,
// host: getIP(), // 资源所在服务器地址
// username: this.loginForm.username, // 用户名
// password: md5pass, // 密码
}).then((res) => {
if (!earthContainer.value) return;
loading.value = false;
// 创建地球实例
earthInstance = new YJ.YJEarth(earthContainer.value);
window.Earth2 = earthInstance;
// 开启右键和左键点击事件
YJ.Global.openRightClick(window.Earth2);
YJ.Global.openLeftClick(window.Earth2);
// 设置初始视角
const view = {
position: {
lng: 102.03643298211526,
lat: 34.393586474501,
alt: 11298179.51993155
},
orientation: {
heading: 360,
pitch: -89.94481747201486,
roll: 0
}
};
YJ.Global.CesiumContainer(window.Earth2, {
compass: false, //罗盘
});
// 加载底图
loadBaseMap(earthInstance.viewer);
// 可以取消注释以下代码来设置初始视角
// YJ.Global.flyTo(earthInstance, view);
// YJ.Global.setDefaultView(earthInstance.viewer, view)
if (props.data.punchRange) {
renderRange(JSON.parse(props.data.punchRange));
}
})
.catch((err) => {
console.error('初始化地球失败:', err);
ElMessage.error('初始化地球失败,请稍后重试');
});
};
// 加载底图
const loadBaseMap = (viewer) => {
if (!viewer || !Cesium) {
ElMessage.error('Cesium库未加载请检查依赖');
return;
}
try {
// 创建瓦片提供器
const imageryProvider = new Cesium.UrlTemplateImageryProvider({
url: 'https://webst01.is.autonavi.com/appmaptile?style=6&x={x}&y={y}&z={z}',
fileExtension: 'png',
minimumLevel: 0,
maximumLevel: 18,
projection: Cesium.WebMercatorProjection,
credit: new Cesium.Credit('卫星图数据来源')
});
// 添加图层到视图
viewer.imageryLayers.addImageryProvider(imageryProvider);
} catch (err) {
console.error('加载底图失败:', err);
ElMessage.error('加载底图失败');
}
};
// 处理数据传递
const handlesubmit = () => {
if (!earthInstance || !earthInstance.viewer) {
ElMessage.warning('地球实例未初始化,无法获取数据');
return;
}
// 要传递的对象数据
const dataToSend = {
...props.data,
punchRange: JSON.stringify(positions.value),
};
if (props.data.id) {
updateAttendanceRange1(dataToSend)
} else {
addAttendanceRange1(dataToSend);
}
emit('send-data', dataToSend);
};
// 新增打卡范围接口
const addAttendanceRange1 = (data) => {
addAttendanceRange(data).then((res) => {
if (res.code === 200) {
ElMessage.success('新增打卡范围成功');
handleClose();
} else {
ElMessage.error(res.msg);
}
}).catch((err) => {
console.error('新增打卡范围失败:', err);
ElMessage.error('新增打卡范围失败');
});
};
// 修改打卡范围接口
const updateAttendanceRange1 = (data) => {
updateAttendanceRange(data).then((res) => {
if (res.code === 200) {
ElMessage.success('修改打卡范围成功');
handleClose();
} else {
ElMessage.error(res.msg);
}
}).catch((err) => {
console.error('修改打卡范围失败:', err);
ElMessage.error('修改打卡范围失败');
});
};
// 绘制范围
const drawRange = () => {
if (!earthInstance || !earthInstance.viewer) {
ElMessage.warning('地球实例未初始化,无法绘制');
return;
}
let draw = new YJ.Draw.DrawPolygon(window.Earth2);
draw.start((err, params) => {
if (err) {
console.error('绘制失败:', err);
ElMessage.error('绘制失败');
return;
}
console.log('绘制成功:', params);
positions.value = params;
renderRange(params);
});
}
// 渲染范围
const renderRange = (params) => {
let option = {
id: 'Checkinrange',
info: {
type: "richText",
text: "",
hrefs: "",
},
positions: params,
color: props.data.punchColor || "rgba(255,0,0,0.5)",
line: {
width: 3,
color: "rgba(255,0,0,1)",
},
type: 0,
show: true,
}
// PolygonObject
let entity = new YJ.Obj.PolygonObject(
window.Earth2,
option
);
entity.flyTo();
entityObject.value = entity
}
// 重新绘制
const redraw = () => {
if (entityObject.value) {
entityObject.value.remove();
}
drawRange();
}
// 组件挂载时设置容器ID
onMounted(() => {
if (!earthContainer.value) {
earthContainer.value = 'earthMap'; // 设置与初始化代码中相同的ID
}
});
// 暴露显示方法给父组件
defineExpose({
show
});
//
onUnmounted(() => {
if (earthInstance) {
earthInstance.destroy();
earthInstance = null;
window.Earth2 = null;
}
});
</script>
<style scoped>
.earth-container {
width: 100%;
height: 600px;
overflow: hidden;
position: relative;
}
:deep(.el-dialog__body) {
padding: 10px;
height: calc(100% - 100px);
overflow: hidden;
}
:deep(.el-dialog) {
display: flex;
flex-direction: column;
max-height: 90vh;
}
:deep(.el-dialog__content) {
flex: 1;
display: flex;
flex-direction: column;
overflow: hidden;
}
</style>

View File

@ -101,6 +101,8 @@ import UserListDialog from '@/views/project/projectTeam/component/UserListDialog
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { team_clock_type } = toRefs<any>(proxy?.useDict('team_clock_type'));
console.log(team_clock_type);
// 获取用户 store
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
@ -113,6 +115,7 @@ const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const projectTeamRangeList = ref([]);
const currentRow = ref<ProjectTeamVO>({
id: undefined,
projectId: undefined,
@ -136,7 +139,8 @@ const initFormData: ProjectTeamForm = {
name: undefined,
isClockIn: undefined,
remark: undefined,
peopleNumber: undefined
peopleNumber: undefined,
punchRangeList: undefined
};
const data = reactive<PageData<ProjectTeamForm, ProjectTeamQuery>>({
form: { ...initFormData },

View File

@ -44,14 +44,14 @@
{{ userDetail?.sfzNumber }}
</el-form-item>
</el-col>
<el-col :span="12">
<!-- <el-col :span="12">
<el-form-item label="身份证号码">
{{ userDetail?.sfzNumber }}
</el-form-item>
</el-col>
</el-col> -->
<el-col :span="12">
<el-form-item label="身份证有效开始期">
{{ dayjs(userDetail?.sfzStart).format('YYYY 年 MM 月 DD 日') }}
{{ dayjs(userDetail?.sfzStart).format('YYYY年 MM 月 DD 日') }}
</el-form-item>
</el-col>
<el-col :span="12">

View File

@ -14,15 +14,6 @@
<el-option v-for="dict in safety_question_type" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item label="题目内容" prop="questionContent">
<el-input v-model="queryParams.questionContent" placeholder="请输入题目内容" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="选项" prop="options">
<el-input v-model="queryParams.options" placeholder="请输入选项" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="正确答案" prop="correctAnswer">
<el-input v-model="queryParams.correctAnswer" placeholder="请输入正确答案" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
@ -38,32 +29,18 @@
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['safety:questionBank:add']"> 新增 </el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['safety:questionBank:edit']"
>修改
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['safety:questionBank:remove']"
>删除
</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['safety:questionBank:export']">导出 </el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="questionBankList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键id" align="center" prop="id" v-if="true" />
<el-table-column label="题目类别" align="center" prop="categoryType">
<el-table v-loading="loading" :data="questionBankList">
<el-table-column label="序号" align="center" type="index" width="80" />
<el-table-column label="题目类别" align="center" width="100" prop="categoryType">
<template #default="scope">
<dict-tag :options="safety_question_category_type" :value="scope.row.categoryType" />
</template>
</el-table-column>
<el-table-column label="题目类型" align="center" prop="questionType">
<el-table-column label="题目类型" align="center" width="100" prop="questionType">
<template #default="scope">
<dict-tag :options="safety_question_type" :value="scope.row.questionType" />
</template>
@ -73,16 +50,11 @@
<el-table-column label="正确答案" align="center" prop="correctAnswer" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['safety:questionBank:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['safety:questionBank:remove']"></el-button>
</el-tooltip>
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['safety:questionBank:edit']">修改</el-button>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['safety:questionBank:remove']">删除</el-button>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改题库对话框 -->

View File

@ -1,7 +1,6 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter"
:leave-active-class="proxy?.animate.searchAnimate.leave">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
@ -31,23 +30,27 @@
:上传压缩包内的文件夹名称需设置为姓名-身份证-满分-得分-及格分 <br />
例如:小明-5130112333654X-100-59-60
</template>
<file-upload :limit="1" v-model:model-value="filePath" isImportInfo :fileType="['zip']"
uploadUrl="/safety/questionUserAnswer/upload/zip" :file-size="5000"
:data="{ projectId: currentProject.id }"><el-button type="success" plain
icon="Upload">上传线下安全考试</el-button></file-upload>
<file-upload
:limit="1"
v-model:model-value="filePath"
isImportInfo
:fileType="['zip']"
uploadUrl="/safety/wgzQuestionSave/upload/zip"
:file-size="5000"
:data="{ projectId: currentProject.id }"
><el-button type="success" plain icon="Upload">上传线下安全考试</el-button></file-upload
>
</el-tooltip>
</el-col>
<el-col :span="1.5">
<el-button type="primary" plain icon="Download" :disabled="single"
@click="handleDownload()">批量下载试卷</el-button>
<el-button type="primary" plain icon="Download" :disabled="single" @click="handleDownload()">批量下载试卷</el-button>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="questionUserAnswerList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键id" align="center" prop="id" v-if="false" />
<el-table-column type="index" label="序号" width="80" align="center" />
<el-table-column label="姓名" align="center" prop="userName" />
<el-table-column label="及格线/总分" align="center" prop="pass" />
<el-table-column label="得分" align="center" prop="score" />
@ -59,21 +62,19 @@
<dict-tag :options="user_exam_type" :value="scope.row.examType" />
</template>
</el-table-column>
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-link type="primary" :underline="false" :href="scope.row.fileUrl[0]" target="_blank">
<!-- <el-link type="primary" :underline="false" :href="scope.row.path[0]" target="_blank">
<el-button link type="primary" icon="View">预览试卷</el-button>
</el-link>
<el-button link type="primary" icon="Download" @click="downloadOssOne(scope.row)"
v-hasPermi="['system:oss:download']">下载试卷</el-button>
</el-link> -->
<el-button link type="primary" icon="Download" @click="downloadOssOne(scope.row)" v-hasPermi="['system:oss:download']"
>下载试卷</el-button
>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum"
v-model:limit="queryParams.pageSize" @pagination="getList" />
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
</div>
</template>
@ -196,35 +197,15 @@ const handleSelectionChange = (selection: QuestionUserAnswerVO[]) => {
multiple.value = !selection.length;
};
/** 修改按钮操作 */
// const handleUpdate = async (row?: QuestionUserAnswerVO) => {
// reset();
// const _id = row?.id || ids.value[0];
// const res = await getQuestionUserAnswer(_id);
// Object.assign(form.value, res.data);
// dialog.visible = true;
// dialog.title = '修改用户试卷存储';
// };
/** 批量下载按钮操作 */
const handleDownload = async () => {
const _ids = ids.value;
await downLoadOss({ idList: _ids }, '/safety/questionUserAnswer/exportFile', '安全考试.zip');
await downLoadOss({ idList: _ids }, '/safety/wgzQuestionSave/exportFile', '安全考试.zip');
};
/** 下载单个按钮操作 */
const downloadOssOne = async (row?: QuestionUserAnswerVO) => {
await download.oss(row?.file);
};
// const fileWatch = watch(
// () => filePath.value,
// (nid, oid) => {
// uploadQuestionUserAnswer({ file: filePath.value, projectId: currentProject.value?.id }).then((res) => {
// console.log(res);
// });
// }
// );
//监听项目id刷新数据
const listeningProject = watch(
() => currentProject.value?.id,

View File

@ -22,38 +22,21 @@
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['safety:questionsCategory:add']">新增</el-button>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['safety:questionsCategory:edit']"
>修改</el-button
>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['safety:questionsCategory:remove']"
>删除</el-button
>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="questionsCategoryList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table v-loading="loading" :data="questionsCategoryList">
<el-table-column label="序号" align="center" type="index" width="100" />
<el-table-column label="题库类别" align="center" prop="categoryName" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['safety:questionsCategory:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['safety:questionsCategory:remove']"
></el-button>
</el-tooltip>
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['safety:questionsCategory:edit']"
>修改</el-button
>
<el-button link type="primary" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['safety:questionsCategory:remove']"
>删除</el-button
>
</template>
</el-table-column>
</el-table>

View File

@ -1,309 +1,220 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="单选题" prop="singleChoice">
<el-input v-model="queryParams.singleChoice" placeholder="请输入单选题" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="单选分数" prop="singleScore">
<el-input v-model="queryParams.singleScore" placeholder="请输入单选分数" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="多选题" prop="multipleChoice">
<el-input v-model="queryParams.multipleChoice" placeholder="请输入多选题" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="多选分数" prop="multipleScore">
<el-input v-model="queryParams.multipleScore" placeholder="请输入多选分数" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="判断题" prop="estimate">
<el-input v-model="queryParams.estimate" placeholder="请输入判断题" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="判断分数" prop="estimateScore">
<el-input v-model="queryParams.estimateScore" placeholder="请输入判断分数" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="满分" prop="fullMark">
<el-input v-model="queryParams.fullMark" placeholder="请输入满分" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="及格线" prop="passScore">
<el-input v-model="queryParams.passScore" placeholder="请输入及格线" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="答题最大时间" prop="answerTime">
<el-input v-model="queryParams.answerTime" placeholder="请输入答题最大时间" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
<div class="container p-4 md:p-6">
<el-card shadow="hover" class="config-card transition-all duration-300 hover:shadow-lg">
<div class="card-header mb-4">
<h2 class="text-xl font-semibold text-gray-800">题库配置</h2>
<p class="text-gray-500 text-sm">设置各类题型数量分值及考试参数</p>
</div>
</transition>
<el-card shadow="never">
<template #header>
<el-row :gutter="10" class="mb8">
<el-col :span="1.5">
<el-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['safety:questionsConfig:add']"> 新增 </el-button>
<el-form :model="form" label-width="120px" ref="formRef" class="config-form">
<el-row :gutter="20">
<!-- 单选题设置 -->
<el-col :span="12" class="form-item-col">
<el-form-item label="单选题(道)" :rules="[{ required: true, message: '单选题数量不能为空', trigger: 'blur' }]">
<el-input type="number" v-model="form.singleChoice" min="0" class="transition-all" placeholder="请输入数量" />
</el-form-item>
</el-col>
<el-col :span="1.5">
<el-button type="success" plain icon="Edit" :disabled="single" @click="handleUpdate()" v-hasPermi="['safety:questionsConfig:edit']"
>修改
</el-button>
<el-col :span="12" class="form-item-col">
<el-form-item label="单选分数" :rules="[{ required: true, message: '单选分数不能为空', trigger: 'blur' }]">
<el-input type="number" v-model="form.singleScore" min="0" class="transition-all" placeholder="每题分数" />
</el-form-item>
</el-col>
<el-col :span="1.5">
<el-button type="danger" plain icon="Delete" :disabled="multiple" @click="handleDelete()" v-hasPermi="['safety:questionsConfig:remove']"
>删除
</el-button>
<!-- 多选题设置 -->
<el-col :span="12" class="form-item-col">
<el-form-item label="多选题(道)" :rules="[{ required: true, message: '多选题数量不能为空', trigger: 'blur' }]">
<el-input type="number" v-model="form.multipleChoice" min="0" class="transition-all" placeholder="请输入数量" />
</el-form-item>
</el-col>
<el-col :span="1.5">
<el-button type="warning" plain icon="Download" @click="handleExport" v-hasPermi="['safety:questionsConfig:export']">导出 </el-button>
<el-col :span="12" class="form-item-col">
<el-form-item label="多选分数" :rules="[{ required: true, message: '多选分数不能为空', trigger: 'blur' }]">
<el-input type="number" v-model="form.multipleScore" min="0" class="transition-all" placeholder="每题分数" />
</el-form-item>
</el-col>
<!-- 判断题设置 -->
<el-col :span="12" class="form-item-col">
<el-form-item label="判断题(道)" :rules="[{ required: true, message: '判断题数量不能为空', trigger: 'blur' }]">
<el-input type="number" v-model="form.estimate" min="0" class="transition-all" placeholder="请输入数量" />
</el-form-item>
</el-col>
<el-col :span="12" class="form-item-col">
<el-form-item label="判断分数" :rules="[{ required: true, message: '判断分数不能为空', trigger: 'blur' }]">
<el-input type="number" v-model="form.estimateScore" min="0" class="transition-all" placeholder="每题分数" />
</el-form-item>
</el-col>
<!-- 分数设置 -->
<el-col :span="12" class="form-item-col">
<el-form-item label="及格分数" :rules="[{ required: true, message: '及格分数不能为空', trigger: 'blur' }]">
<el-input
type="number"
v-model="form.passingScore"
min="0"
:max="form.fullMark"
@change="handlePassingScoreChange"
class="transition-all"
placeholder="请输入及格分数"
/>
</el-form-item>
</el-col>
<el-col :span="12" class="form-item-col">
<el-form-item label="满分">
<el-input type="number" v-model="form.fullMark" disabled class="bg-gray-50" />
</el-form-item>
</el-col>
<!-- 时间设置 -->
<el-col :span="12" class="form-item-col">
<el-form-item label="答题时间(分钟)" :rules="[{ required: true, message: '答题时间不能为空', trigger: 'blur' }]">
<el-input type="number" v-model="form.answerTime" min="0" class="transition-all" placeholder="请输入时间" />
</el-form-item>
</el-col>
<right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar>
</el-row>
</template>
<el-table v-loading="loading" :data="questionsConfigList" @selection-change="handleSelectionChange">
<el-table-column type="selection" width="55" align="center" />
<el-table-column label="主键id" align="center" prop="id" v-if="true" />
<el-table-column label="单选题" align="center" prop="singleChoice" />
<el-table-column label="单选分数" align="center" prop="singleScore" />
<el-table-column label="多选题" align="center" prop="multipleChoice" />
<el-table-column label="多选分数" align="center" prop="multipleScore" />
<el-table-column label="判断题" align="center" prop="estimate" />
<el-table-column label="判断分数" align="center" prop="estimateScore" />
<el-table-column label="满分" align="center" prop="fullMark" />
<el-table-column label="及格线" align="center" prop="passScore" />
<el-table-column label="答题最大时间" align="center" prop="answerTime" />
<el-table-column label="操作" align="center" class-name="small-padding fixed-width">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button link type="primary" icon="Edit" @click="handleUpdate(scope.row)" v-hasPermi="['safety:questionsConfig:edit']"></el-button>
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button
link
type="primary"
icon="Delete"
@click="handleDelete(scope.row)"
v-hasPermi="['safety:questionsConfig:remove']"
></el-button>
</el-tooltip>
</template>
</el-table-column>
</el-table>
<pagination v-show="total > 0" :total="total" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" @pagination="getList" />
</el-card>
<!-- 添加或修改题库配置对话框 -->
<el-dialog :title="dialog.title" v-model="dialog.visible" width="500px" append-to-body>
<el-form ref="questionsConfigFormRef" :model="form" :rules="rules" label-width="80px">
<el-form-item label="单选题" prop="singleChoice">
<el-input v-model="form.singleChoice" placeholder="请输入单选题" />
</el-form-item>
<el-form-item label="单选分数" prop="singleScore">
<el-input v-model="form.singleScore" placeholder="请输入单选分数" />
</el-form-item>
<el-form-item label="多选题" prop="multipleChoice">
<el-input v-model="form.multipleChoice" placeholder="请输入多选题" />
</el-form-item>
<el-form-item label="多选分数" prop="multipleScore">
<el-input v-model="form.multipleScore" placeholder="请输入多选分数" />
</el-form-item>
<el-form-item label="判断题" prop="estimate">
<el-input v-model="form.estimate" placeholder="请输入判断题" />
</el-form-item>
<el-form-item label="判断分数" prop="estimateScore">
<el-input v-model="form.estimateScore" placeholder="请输入判断分数" />
</el-form-item>
<el-form-item label="满分" prop="fullMark">
<el-input v-model="form.fullMark" placeholder="请输入满分" />
</el-form-item>
<el-form-item label="及格线" prop="passScore">
<el-input v-model="form.passScore" placeholder="请输入及格线" />
</el-form-item>
<el-form-item label="答题最大时间" prop="answerTime">
<el-input v-model="form.answerTime" placeholder="请输入答题最大时间" />
</el-form-item>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button :loading="buttonLoading" type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
<div class="form-actions flex justify-center mt-6">
<el-button type="primary" @click="onSubmit" :loading="buttonLoading" class="px-8 transition-all hover:scale-105"> 保存配置 </el-button>
</div>
</el-card>
</div>
</template>
<script setup name="QuestionsConfig" lang="ts">
import { addQuestionsConfig, delQuestionsConfig, getQuestionsConfig, listQuestionsConfig, updateQuestionsConfig } from '@/api/safety/questionsConfig';
import { QuestionsConfigForm, QuestionsConfigQuery, QuestionsConfigVO } from '@/api/safety/questionsConfig/types';
import { ref, reactive, computed, onMounted, getCurrentInstance } from 'vue';
import { ElFormInstance, ComponentInternalInstance } from 'element-plus';
import { getQuestionsConfig, updateQuestionsConfig } from '@/api/safety/questionsConfig';
import { QuestionsConfigForm} from '@/api/safety/questionsConfig/types';
import { useUserStoreHook } from '@/store/modules/user';
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
// 获取用户 store
// 获取用户信息
const userStore = useUserStoreHook();
// 从 store 中获取项目列表和当前选中的项目
const currentProject = computed(() => userStore.selectedProject);
const questionsConfigList = ref<QuestionsConfigVO[]>([]);
// 表单相关
const formRef = ref<ElFormInstance>();
const buttonLoading = ref(false);
const loading = ref(true);
const showSearch = ref(true);
const ids = ref<Array<string | number>>([]);
const single = ref(true);
const multiple = ref(true);
const total = ref(0);
const queryFormRef = ref<ElFormInstance>();
const questionsConfigFormRef = ref<ElFormInstance>();
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const initFormData: QuestionsConfigForm = {
const form = reactive<QuestionsConfigForm>({
id: undefined,
projectId: currentProject.value?.id,
singleChoice: undefined,
singleScore: undefined,
multipleChoice: undefined,
multipleScore: undefined,
estimate: undefined,
estimateScore: undefined,
fullMark: undefined,
passScore: undefined,
answerTime: undefined
};
const data = reactive<PageData<QuestionsConfigForm, QuestionsConfigQuery>>({
form: { ...initFormData },
queryParams: {
pageNum: 1,
pageSize: 10,
projectId: currentProject.value?.id,
singleChoice: undefined,
singleScore: undefined,
multipleChoice: undefined,
multipleScore: undefined,
estimate: undefined,
estimateScore: undefined,
fullMark: undefined,
passScore: undefined,
answerTime: undefined,
params: {}
},
rules: {
id: [{ required: true, message: '主键id不能为空', trigger: 'blur' }],
singleChoice: [{ required: true, message: '单选题不能为空', trigger: 'blur' }],
singleScore: [{ required: true, message: '单选分数不能为空', trigger: 'blur' }],
multipleChoice: [{ required: true, message: '多选题不能为空', trigger: 'blur' }],
multipleScore: [{ required: true, message: '多选分数不能为空', trigger: 'blur' }],
estimate: [{ required: true, message: '判断题不能为空', trigger: 'blur' }],
estimateScore: [{ required: true, message: '判断分数不能为空', trigger: 'blur' }],
fullMark: [{ required: true, message: '满分不能为空', trigger: 'blur' }],
passScore: [{ required: true, message: '及格线不能为空', trigger: 'blur' }],
answerTime: [{ required: true, message: '答题最大时间不能为空', trigger: 'blur' }]
}
singleChoice: 0,
singleScore: 0,
multipleChoice: 0,
multipleScore: 0,
estimate: 0,
estimateScore: 0,
fullMark: 0,
passScore: 0,
answerTime: 0,
passingScore: 0
});
const { queryParams, form, rules } = toRefs(data);
// 计算满分
const calculateFullMark = () => {
form.fullMark =
(form.singleChoice || 0) * (form.singleScore || 0) +
(form.multipleChoice || 0) * (form.multipleScore || 0) +
(form.estimate || 0) * (form.estimateScore || 0);
/** 查询题库配置列表 */
const getList = async () => {
loading.value = true;
const res = await listQuestionsConfig(queryParams.value);
questionsConfigList.value = res.rows;
total.value = res.total;
loading.value = false;
// 确保及格分不超过满分
if (form.passingScore > form.fullMark) {
form.passingScore = form.fullMark;
}
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
// 监听分数变化,自动计算满分
['singleChoice', 'singleScore', 'multipleChoice', 'multipleScore', 'estimate', 'estimateScore'].forEach((field) => {
watch(() => form[field], calculateFullMark);
});
// 处理及格分变化
const handlePassingScoreChange = (val: number) => {
if (val >= form.fullMark) {
form.passingScore = form.fullMark;
}
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
questionsConfigFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
queryParams.value.pageNum = 1;
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 多选框选中数据 */
const handleSelectionChange = (selection: QuestionsConfigVO[]) => {
ids.value = selection.map((item) => item.id);
single.value = selection.length != 1;
multiple.value = !selection.length;
};
/** 新增按钮操作 */
const handleAdd = () => {
reset();
dialog.visible = true;
dialog.title = '添加题库配置';
};
/** 修改按钮操作 */
const handleUpdate = async (row?: QuestionsConfigVO) => {
reset();
const _id = row?.id || ids.value[0];
const res = await getQuestionsConfig(_id);
Object.assign(form.value, res.data);
dialog.visible = true;
dialog.title = '修改题库配置';
};
/** 提交按钮 */
const submitForm = () => {
questionsConfigFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
if (form.value.id) {
await updateQuestionsConfig(form.value).finally(() => (buttonLoading.value = false));
} else {
await addQuestionsConfig(form.value).finally(() => (buttonLoading.value = false));
}
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
// 获取配置详情
const getDetail = () => {
getQuestionsConfig(1).then((res: any) => {
if (res.code == 200 && res.data) {
Object.assign(form, res.data);
calculateFullMark(); // 确保满分计算正确
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row?: QuestionsConfigVO) => {
const _ids = row?.id || ids.value;
await proxy?.$modal.confirm('是否确认删除题库配置编号为"' + _ids + '"的数据项?').finally(() => (loading.value = false));
await delQuestionsConfig(_ids);
proxy?.$modal.msgSuccess('删除成功');
await getList();
};
/** 导出按钮操作 */
const handleExport = () => {
proxy?.download(
'safety/questionsConfig/export',
{
...queryParams.value
},
`questionsConfig_${new Date().getTime()}.xlsx`
);
// 提交表单
const onSubmit = () => {
formRef.value?.validate(async (valid: boolean) => {
if (valid) {
buttonLoading.value = true;
try {
await updateQuestionsConfig(form);
// 显示成功消息假设使用element-plus的message组件
ElMessage.success('配置保存成功');
getDetail(); // 重新获取最新配置
} catch (error) {
ElMessage.error('保存失败,请重试');
} finally {
buttonLoading.value = false;
}
}
});
};
// 页面加载时获取数据
onMounted(() => {
getList();
getDetail();
});
</script>
<style lang="scss" scoped>
.container {
max-width: 800px;
margin: 0 auto;
padding-top: 20px;
}
.config-card {
border-radius: 12px;
overflow: hidden;
}
.card-header {
border-bottom: 1px solid #f0f0f0;
padding-bottom: 12px;
}
.form-item-col {
margin-bottom: 16px;
}
.form-actions {
padding: 16px;
border-top: 1px solid #f0f0f0;
margin-top: 20px;
}
// 输入框聚焦效果
::v-deep .el-input__wrapper:focus-within {
box-shadow: 0 0 0 2px rgba(48, 163, 255, 0.2);
}
// 响应式调整
@media (max-width: 768px) {
.el-col {
&:span-12 {
width: 100% !important;
}
}
.card-header {
text-align: center;
}
}
</style>

View File

@ -0,0 +1,413 @@
<template>
<div class="p-2">
<transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
<div v-show="showSearch" class="mb-[10px]">
<el-card shadow="hover">
<el-form ref="queryFormRef" :model="queryParams" :inline="true">
<el-form-item label="菜单名称" prop="menuName">
<el-input v-model="queryParams.menuName" placeholder="请输入菜单名称" clearable @keyup.enter="handleQuery" />
</el-form-item>
<el-form-item label="状态" prop="status">
<el-select v-model="queryParams.status" placeholder="菜单状态" clearable>
<el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.label" :value="dict.value" />
</el-select>
</el-form-item>
<el-form-item>
<el-button type="primary" icon="Search" @click="handleQuery">搜索</el-button>
<el-button icon="Refresh" @click="resetQuery">重置</el-button>
</el-form-item>
</el-form>
</el-card>
</div>
</transition>
<el-card shadow="hover">
<template #header>
<el-row :gutter="10">
<el-col :span="1.5">
<el-button v-hasPermi="['system:menu:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增 </el-button>
</el-col>
<el-col :span="1.5">
<el-button type="info" plain icon="Sort" @click="handleToggleExpandAll">展开/折叠</el-button>
</el-col>
<right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar>
</el-row>
</template>
<el-table
ref="menuTableRef"
v-loading="loading"
:data="menuList"
row-key="menuId"
:tree-props="{ children: 'children', hasChildren: 'hasChildren' }"
:default-expand-all="isExpandAll"
>
<el-table-column prop="menuName" label="菜单名称" :show-overflow-tooltip="true" width="160"></el-table-column>
<el-table-column prop="icon" label="图标" align="center" width="100">
<template #default="scope">
<svg-icon :icon-class="scope.row.icon" />
</template>
</el-table-column>
<el-table-column prop="orderNum" label="排序" width="60"></el-table-column>
<el-table-column prop="perms" label="权限标识" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="component" label="组件路径" :show-overflow-tooltip="true"></el-table-column>
<el-table-column prop="status" label="状态" width="80">
<template #default="scope">
<dict-tag :options="sys_normal_disable" :value="scope.row.status" />
</template>
</el-table-column>
<el-table-column label="创建时间" align="center" prop="createTime">
<template #default="scope">
<span>{{ scope.row.createTime }}</span>
</template>
</el-table-column>
<el-table-column fixed="right" label="操作" width="180">
<template #default="scope">
<el-tooltip content="修改" placement="top">
<el-button v-hasPermi="['system:menu:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)" />
</el-tooltip>
<el-tooltip content="新增" placement="top">
<el-button v-hasPermi="['system:menu:add']" link type="primary" icon="Plus" @click="handleAdd(scope.row)" />
</el-tooltip>
<el-tooltip content="删除" placement="top">
<el-button v-hasPermi="['system:menu:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)" />
</el-tooltip>
</template>
</el-table-column>
</el-table>
</el-card>
<el-dialog v-model="dialog.visible" :title="dialog.title" destroy-on-close append-to-bod width="750px">
<el-form ref="menuFormRef" :model="form" :rules="rules" label-width="100px">
<el-row>
<el-col :span="24">
<el-form-item label="上级菜单">
<el-tree-select
v-model="form.parentId"
:data="menuOptions"
:props="{ value: 'menuId', label: 'menuName', children: 'children' }"
value-key="menuId"
placeholder="选择上级菜单"
check-strictly
/>
</el-form-item>
</el-col>
<el-col :span="24">
<el-form-item label="菜单类型" prop="menuType">
<el-radio-group v-model="form.menuType">
<el-radio value="M">目录</el-radio>
<el-radio value="C">菜单</el-radio>
<el-radio value="F">按钮</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'F'" :span="24">
<el-form-item label="菜单图标" prop="icon">
<!-- 图标选择器 -->
<icon-select v-model="form.icon" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="菜单名称" prop="menuName">
<el-input v-model="form.menuName" placeholder="请输入菜单名称" />
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item label="显示排序" prop="orderNum">
<el-input-number v-model="form.orderNum" controls-position="right" :min="0" />
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'F'" :span="12">
<el-form-item>
<template #label>
<span>
<el-tooltip content="选择是外链则路由地址需要以`http(s)://`开头" placement="top">
<el-icon>
<question-filled />
</el-icon> </el-tooltip
>是否外链
</span>
</template>
<el-radio-group v-model="form.isFrame">
<el-radio label="0"></el-radio>
<el-radio label="1"></el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'F'" :span="12">
<el-form-item prop="path">
<template #label>
<span>
<el-tooltip content="访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
路由地址
</span>
</template>
<el-input v-model="form.path" placeholder="请输入路由地址" />
</el-form-item>
</el-col>
<el-col v-if="form.menuType === 'C'" :span="12">
<el-form-item prop="component">
<template #label>
<span>
<el-tooltip content="访问的组件路径,如:`system/user/index`,默认在`views`目录下" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
组件路径
</span>
</template>
<el-input v-model="form.component" placeholder="请输入组件路径" />
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'M'" :span="12">
<el-form-item>
<el-input v-model="form.perms" placeholder="请输入权限标识" maxlength="100" />
<template #label>
<span>
<el-tooltip content="控制器中定义的权限字符,如:@SaCheckPermission('system:user:list')" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
权限字符
</span>
</template>
</el-form-item>
</el-col>
<el-col v-if="form.menuType === 'C'" :span="12">
<el-form-item>
<el-input v-model="form.queryParam" placeholder="请输入路由参数" maxlength="255" />
<template #label>
<span>
<el-tooltip content='访问路由的默认传递参数,如:`{"id": 1, "name": "ry"}`' placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
路由参数
</span>
</template>
</el-form-item>
</el-col>
<el-col v-if="form.menuType === 'C'" :span="12">
<el-form-item>
<template #label>
<span>
<el-tooltip content="选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
是否缓存
</span>
</template>
<el-radio-group v-model="form.isCache">
<el-radio label="0">缓存</el-radio>
<el-radio label="1">不缓存</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col v-if="form.menuType !== 'F'" :span="12">
<el-form-item>
<template #label>
<span>
<el-tooltip content="选择隐藏则路由将不会出现在侧边栏,但仍然可以访问" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
显示状态
</span>
</template>
<el-radio-group v-model="form.visible">
<el-radio v-for="dict in sys_show_hide" :key="dict.value" :label="dict.value">{{ dict.label }} </el-radio>
</el-radio-group>
</el-form-item>
</el-col>
<el-col :span="12">
<el-form-item>
<template #label>
<span>
<el-tooltip content="选择停用则路由将不会出现在侧边栏,也不能被访问" placement="top">
<el-icon>
<question-filled />
</el-icon>
</el-tooltip>
菜单状态
</span>
</template>
<el-radio-group v-model="form.status">
<el-radio v-for="dict in sys_normal_disable" :key="dict.value" :label="dict.value">
{{ dict.label }}
</el-radio>
</el-radio-group>
</el-form-item>
</el-col>
</el-row>
</el-form>
<template #footer>
<div class="dialog-footer">
<el-button type="primary" @click="submitForm"> </el-button>
<el-button @click="cancel"> </el-button>
</div>
</template>
</el-dialog>
</div>
</template>
<script setup name="Menu" lang="ts">
import { addMenu, delMenu, getMenu, listMenu, updateMenu } from '@/api/system/menu';
import { MenuForm, MenuQuery, MenuVO } from '@/api/system/menu/types';
import { MenuTypeEnum } from '@/enums/MenuTypeEnum';
interface MenuOptionsType {
menuId: number;
menuName: string;
children: MenuOptionsType[] | undefined;
}
const { proxy } = getCurrentInstance() as ComponentInternalInstance;
const { sys_show_hide, sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_show_hide', 'sys_normal_disable'));
const menuList = ref<MenuVO[]>([]);
const loading = ref(true);
const showSearch = ref(true);
const menuOptions = ref<MenuOptionsType[]>([]);
const isExpandAll = ref(false);
const dialog = reactive<DialogOption>({
visible: false,
title: ''
});
const queryFormRef = ref<ElFormInstance>();
const menuFormRef = ref<ElFormInstance>();
const initFormData = {
path: '',
menuId: undefined,
parentId: 0,
menuName: '',
icon: '',
menuType: MenuTypeEnum.M,
orderNum: 1,
isFrame: '1',
isCache: '0',
visible: '0',
status: '0',
menuSource: '2'
};
const data = reactive<PageData<MenuForm, MenuQuery>>({
form: { ...initFormData },
queryParams: {
menuName: undefined,
status: undefined,
menuSource: 2
},
rules: {
menuName: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }],
orderNum: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }],
path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }]
}
});
const menuTableRef = ref<ElTableInstance>();
const { queryParams, form, rules } = toRefs<PageData<MenuForm, MenuQuery>>(data);
/** 查询菜单列表 */
const getList = async () => {
loading.value = true;
const res = await listMenu(queryParams.value);
const data = proxy?.handleTree<MenuVO>(res.data, 'menuId');
if (data) {
menuList.value = data;
}
loading.value = false;
};
/** 查询菜单下拉树结构 */
const getTreeselect = async () => {
menuOptions.value = [];
const response = await listMenu();
const menu: MenuOptionsType = { menuId: 0, menuName: '主类目', children: [] };
menu.children = proxy?.handleTree<MenuOptionsType>(response.data, 'menuId');
menuOptions.value.push(menu);
};
/** 取消按钮 */
const cancel = () => {
reset();
dialog.visible = false;
};
/** 表单重置 */
const reset = () => {
form.value = { ...initFormData };
menuFormRef.value?.resetFields();
};
/** 搜索按钮操作 */
const handleQuery = () => {
getList();
};
/** 重置按钮操作 */
const resetQuery = () => {
queryFormRef.value?.resetFields();
handleQuery();
};
/** 新增按钮操作 */
const handleAdd = (row?: MenuVO) => {
reset();
getTreeselect();
row && row.menuId ? (form.value.parentId = row.menuId) : (form.value.parentId = 0);
dialog.visible = true;
dialog.title = '添加菜单';
};
/** 展开/折叠操作 */
const handleToggleExpandAll = () => {
isExpandAll.value = !isExpandAll.value;
toggleExpandAll(menuList.value, isExpandAll.value);
};
/** 展开/折叠所有 */
const toggleExpandAll = (data: MenuVO[], status: boolean) => {
data.forEach((item: MenuVO) => {
menuTableRef.value?.toggleRowExpansion(item, status);
if (item.children && item.children.length > 0) toggleExpandAll(item.children, status);
});
};
/** 修改按钮操作 */
const handleUpdate = async (row: MenuVO) => {
reset();
await getTreeselect();
if (row.menuId) {
const { data } = await getMenu(row.menuId);
form.value = data;
}
dialog.visible = true;
dialog.title = '修改菜单';
};
/** 提交按钮 */
const submitForm = () => {
menuFormRef.value?.validate(async (valid: boolean) => {
if (valid) {
form.value.menuId ? await updateMenu(form.value) : await addMenu(form.value);
proxy?.$modal.msgSuccess('操作成功');
dialog.visible = false;
await getList();
}
});
};
/** 删除按钮操作 */
const handleDelete = async (row: MenuVO) => {
await proxy?.$modal.confirm('是否确认删除名称为"' + row.menuName + '"的数据项?');
await delMenu(row.menuId);
await getList();
proxy?.$modal.msgSuccess('删除成功');
};
onMounted(() => {
getList();
});
</script>

Some files were not shown because too many files have changed in this diff Show More