diff --git a/.env.development b/.env.development index faff5a9..627f02a 100644 --- a/.env.development +++ b/.env.development @@ -5,14 +5,20 @@ VITE_APP_TITLE = 煤科建管平台 VITE_APP_ENV = 'development' # 开发环境 +VITE_APP_BASE_API = 'http://192.168.110.149:8899' # 李陈杰 209 +<<<<<<< HEAD VITE_APP_BASE_API = 'http://192.168.110.210:8899' +======= + +# VITE_APP_BASE_API = 'http://192.168.110.209:8899' +>>>>>>> 3e4b85fc48f6514704aee4b444285b90935b0cd2 # 曾涛 -# VITE_APP_BASE_API = 'http://192.168.110.180:8899' + # 罗成 # VITE_APP_BASE_API = 'http://192.168.110.188:8899' # 朱银 -# VITE_APP_BASE_API = 'http://192.168.110.149:8899' +# VITE_APP_BASE_API = 'http://192.168.110.180:8899' #曾涛 # VITE_APP_BASE_API = 'http://192.168.110.171:8899' diff --git a/absolute/path/to/index.vue b/absolute/path/to/index.vue new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/absolute/path/to/index.vue @@ -0,0 +1 @@ + diff --git a/package.json b/package.json index 58746c4..62a70bc 100644 --- a/package.json +++ b/package.json @@ -69,7 +69,8 @@ "vue-types": "5.1.3", "vue3-print-nb": "^0.1.4", "vue3-scroll-seamless": "^1.0.6", - "vxe-table": "4.5.22" + "vxe-table": "4.5.22", + "xlsx": "^0.18.5" }, "devDependencies": { "@eslint/js": "9.15.0", diff --git a/public/catalog.xlsx b/public/catalog.xlsx index 7ebf378..eb9e383 100644 Binary files a/public/catalog.xlsx and b/public/catalog.xlsx differ diff --git a/src/api/design/received/index.ts b/src/api/design/received/index.ts index f8bbbdd..848f53a 100644 --- a/src/api/design/received/index.ts +++ b/src/api/design/received/index.ts @@ -24,3 +24,11 @@ export const exportWord = (params) => { method: 'post' }); }; +// 导出模版 +export const exportExcel = (params) => { + return request({ + url: '/design/collect/exportExcel', + method: 'post', + params: params + }); +}; diff --git a/src/api/design/volumeCatalog/index.ts b/src/api/design/volumeCatalog/index.ts index be533ae..aff48d6 100644 --- a/src/api/design/volumeCatalog/index.ts +++ b/src/api/design/volumeCatalog/index.ts @@ -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 diff --git a/src/api/equipment/index.ts b/src/api/equipment/index.ts new file mode 100644 index 0000000..7a5992d --- /dev/null +++ b/src/api/equipment/index.ts @@ -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 => { + return request({ + url: '/gps/equipment/list', + method: 'get', + params: query + }); +}; + +/** + * 查询GPS设备详细详细 + * @param id + */ +export const getEquipment = (id: string | number): AxiosPromise => { + 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) => { + 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 + }); +}; diff --git a/src/api/equipment/types.ts b/src/api/equipment/types.ts new file mode 100644 index 0000000..71ec89b --- /dev/null +++ b/src/api/equipment/types.ts @@ -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; +} diff --git a/src/api/materials/company/index.ts b/src/api/materials/company/index.ts index da21b9f..1996df8 100644 --- a/src/api/materials/company/index.ts +++ b/src/api/materials/company/index.ts @@ -14,6 +14,18 @@ export const listCompany = (query?: CompanyQuery): AxiosPromise => method: 'get', params: query }); +}; /** + * 查询材料提供商 + * @param query + * @returns {*} + */ + +export const supplierInputGet = (query?) => { + return request({ + url: '/supplierInput/supplierInput/getList', + method: 'get', + params: query + }); }; /** diff --git a/src/api/materials/materialIssue/index.ts b/src/api/materials/materialIssue/index.ts index 99af31a..33a986a 100644 --- a/src/api/materials/materialIssue/index.ts +++ b/src/api/materials/materialIssue/index.ts @@ -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' + }); +}; diff --git a/src/api/materials/materialOutbound/index.ts b/src/api/materials/materialOutbound/index.ts new file mode 100644 index 0000000..ca53c82 --- /dev/null +++ b/src/api/materials/materialOutbound/index.ts @@ -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 + }); +}; diff --git a/src/api/materials/materialsUseRecord/types.ts b/src/api/materials/materialsUseRecord/types.ts index 697f347..19df92b 100644 --- a/src/api/materials/materialsUseRecord/types.ts +++ b/src/api/materials/materialsUseRecord/types.ts @@ -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; } - - - diff --git a/src/api/out/monthPlan/index.ts b/src/api/out/monthPlan/index.ts index 48f376f..cf59628 100644 --- a/src/api/out/monthPlan/index.ts +++ b/src/api/out/monthPlan/index.ts @@ -8,7 +8,7 @@ import { MonthPlanVO, MonthPlanForm, MonthPlanQuery } from '@/api/out/monthPlan/ * @returns {*} */ -export const listMonthPlan = (query?: MonthPlanQuery): AxiosPromise => { +export const listMonthPlan = (query?: any) => { return request({ url: '/out/monthPlan/list', method: 'get', @@ -120,4 +120,4 @@ export const purchaseValueA = (query) => { method: 'get', params: query }); -}; \ No newline at end of file +}; diff --git a/src/api/project/constructionUser/index.ts b/src/api/project/constructionUser/index.ts index 35ec956..c3c543c 100644 --- a/src/api/project/constructionUser/index.ts +++ b/src/api/project/constructionUser/index.ts @@ -203,3 +203,31 @@ 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 + }); +}; \ No newline at end of file diff --git a/src/api/project/constructionUser/types.ts b/src/api/project/constructionUser/types.ts index b65e23f..47acc77 100644 --- a/src/api/project/constructionUser/types.ts +++ b/src/api/project/constructionUser/types.ts @@ -182,6 +182,8 @@ export interface ConstructionUserVO { * 创建时间 */ createTime: string; + + sysUserId: string | number; } export interface skipType { /** diff --git a/src/api/project/project/index.ts b/src/api/project/project/index.ts index cd3d1d7..23cd9ad 100644 --- a/src/api/project/project/index.ts +++ b/src/api/project/project/index.ts @@ -195,3 +195,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 + }); +}; \ No newline at end of file diff --git a/src/api/project/projectTeam/index.ts b/src/api/project/projectTeam/index.ts index 8a55f69..8d0b39a 100644 --- a/src/api/project/projectTeam/index.ts +++ b/src/api/project/projectTeam/index.ts @@ -72,3 +72,12 @@ export const delProjectTeam = (id: string | number | Array) => method: 'delete' }); }; + +// 获取项目得打卡范围 +export const getProjectTeamClockIn = (params) => { + return request({ + url: '/project/projectTeam/rangeList', + method: 'get', + params + }); +}; \ No newline at end of file diff --git a/src/api/project/projectTeam/types.ts b/src/api/project/projectTeam/types.ts index d14b582..aa66f35 100644 --- a/src/api/project/projectTeam/types.ts +++ b/src/api/project/projectTeam/types.ts @@ -55,6 +55,11 @@ export interface ProjectTeamForm extends BaseEntity { * 备注 */ remark?: string; + + /** + * 创建时间 + */ + punchRangeList?: []; } export interface ProjectTeamQuery extends PageQuery { diff --git a/src/api/projectScreen/index.ts b/src/api/projectScreen/index.ts new file mode 100644 index 0000000..9e84e68 --- /dev/null +++ b/src/api/projectScreen/index.ts @@ -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', + }); +}; diff --git a/src/api/projectScreen/types.ts b/src/api/projectScreen/types.ts new file mode 100644 index 0000000..f6f3b57 --- /dev/null +++ b/src/api/projectScreen/types.ts @@ -0,0 +1,5 @@ +export interface TableQuery extends PageQuery { + tableName: string; + tableComment: string; + dataName: string; +} \ No newline at end of file diff --git a/src/api/system/landTransfer/landTransferLedger/index.ts b/src/api/system/landTransfer/landTransferLedger/index.ts index fbd949e..bd397a1 100644 --- a/src/api/system/landTransfer/landTransferLedger/index.ts +++ b/src/api/system/landTransfer/landTransferLedger/index.ts @@ -79,3 +79,26 @@ export const landTransferLedgerCount = (id: string | number | Array { + 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' + }); +}; diff --git a/src/api/system/landTransfer/landTransferLedger/types.ts b/src/api/system/landTransfer/landTransferLedger/types.ts index 75487d0..59daeb4 100644 --- a/src/api/system/landTransfer/landTransferLedger/types.ts +++ b/src/api/system/landTransfer/landTransferLedger/types.ts @@ -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; } - - - diff --git a/src/api/system/menu/index.ts b/src/api/system/menu/index.ts index 15cf1b5..2866271 100644 --- a/src/api/system/menu/index.ts +++ b/src/api/system/menu/index.ts @@ -20,18 +20,20 @@ export const getMenu = (menuId: string | number): AxiosPromise => { }; // 查询菜单下拉树结构 -export const treeselect = (): AxiosPromise => { +export const treeselect = (params?: any): AxiosPromise => { return request({ url: '/system/menu/treeselect', - method: 'get' + method: 'get', + params }); }; // 根据角色ID查询菜单下拉树结构 -export const roleMenuTreeselect = (roleId: string | number): AxiosPromise => { +export const roleMenuTreeselect = (roleId: string | number, params?: any): AxiosPromise => { return request({ url: '/system/menu/roleMenuTreeselect/' + roleId, - method: 'get' + method: 'get', + params }); }; diff --git a/src/api/system/role/index.ts b/src/api/system/role/index.ts index fb0fcab..6727852 100644 --- a/src/api/system/role/index.ts +++ b/src/api/system/role/index.ts @@ -147,10 +147,11 @@ export const authUserSelectAll = (data: any) => { }); }; // 根据角色ID查询部门树结构 -export const deptTreeSelect = (roleId: string | number): AxiosPromise => { +export const deptTreeSelect = (roleId: string | number, params?) => { return request({ url: '/system/role/deptTree/' + roleId, - method: 'get' + method: 'get', + params }); }; diff --git a/src/assets/projectLarge/center.png b/src/assets/projectLarge/center.png new file mode 100644 index 0000000..c1ea62c Binary files /dev/null and b/src/assets/projectLarge/center.png differ diff --git a/src/assets/projectLarge/leftarrow.png b/src/assets/projectLarge/leftarrow.png deleted file mode 100644 index 55c4b79..0000000 Binary files a/src/assets/projectLarge/leftarrow.png and /dev/null differ diff --git a/src/assets/projectLarge/rightarrow.png b/src/assets/projectLarge/rightarrow.png deleted file mode 100644 index 723df81..0000000 Binary files a/src/assets/projectLarge/rightarrow.png and /dev/null differ diff --git a/src/assets/projectLarge/swiper.png b/src/assets/projectLarge/swiper.png deleted file mode 100644 index 32520bd..0000000 Binary files a/src/assets/projectLarge/swiper.png and /dev/null differ diff --git a/src/layout/components/Navbar.vue b/src/layout/components/Navbar.vue index 8e70d3f..c2ed27e 100644 --- a/src/layout/components/Navbar.vue +++ b/src/layout/components/Navbar.vue @@ -26,11 +26,11 @@ - +
diff --git a/src/utils/projectTeam.ts b/src/utils/projectTeam.ts index 23f482d..c0c8d23 100644 --- a/src/utils/projectTeam.ts +++ b/src/utils/projectTeam.ts @@ -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 { id } = $cache.local.getJSON('selectedProject'); diff --git a/src/views/ProjectScreen/components/centerPage.vue b/src/views/ProjectScreen/components/centerPage.vue index 8c79dcf..50ab572 100644 --- a/src/views/ProjectScreen/components/centerPage.vue +++ b/src/views/ProjectScreen/components/centerPage.vue @@ -1,26 +1,27 @@ - @@ -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; } } diff --git a/src/views/ProjectScreen/components/header.vue b/src/views/ProjectScreen/components/header.vue index 322f1d4..5450f69 100644 --- a/src/views/ProjectScreen/components/header.vue +++ b/src/views/ProjectScreen/components/header.vue @@ -6,7 +6,7 @@
安全生产天数:
- 1,235 + {{ safetyDay }}
@@ -14,16 +14,18 @@
XXX智慧工地管理平台
XXX Smart Construction Stic Management Dashboard
-
+
- 天气图标 - - - 多云 9°/18° - {{ week[date.week] }} ({{ date.ymd }}) - +
+
+ +
{{ item.weather }}{{ item.tempMin }}°/{{ item.tempMax }}°
+
{{ item.week }}({{ item.date }})
+
+
@@ -35,48 +37,155 @@ 设置图标 管理系统
+ +
+
+
+
+ +
+ + + + + + +
diff --git a/src/views/ProjectScreen/components/leftPage.vue b/src/views/ProjectScreen/components/leftPage.vue index 3be8ba0..8336379 100644 --- a/src/views/ProjectScreen/components/leftPage.vue +++ b/src/views/ProjectScreen/components/leftPage.vue @@ -2,30 +2,53 @@
- <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> diff --git a/src/views/ProjectScreen/components/optionList.ts b/src/views/ProjectScreen/components/optionList.ts index e69de29..bcdf958 100644 --- a/src/views/ProjectScreen/components/optionList.ts +++ b/src/views/ProjectScreen/components/optionList.ts @@ -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] } + ], + } + ] +}; diff --git a/src/views/ProjectScreen/components/rightPage.vue b/src/views/ProjectScreen/components/rightPage.vue index 52d13b0..0d08c08 100644 --- a/src/views/ProjectScreen/components/rightPage.vue +++ b/src/views/ProjectScreen/components/rightPage.vue @@ -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); } diff --git a/src/views/ProjectScreen/components/title.vue b/src/views/ProjectScreen/components/title.vue index 04b607c..361f557 100644 --- a/src/views/ProjectScreen/components/title.vue +++ b/src/views/ProjectScreen/components/title.vue @@ -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> diff --git a/src/views/ProjectScreen/index.vue b/src/views/ProjectScreen/index.vue index 612dd7d..f58a18f 100644 --- a/src/views/ProjectScreen/index.vue +++ b/src/views/ProjectScreen/index.vue @@ -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> diff --git a/src/views/biddingManagemen/biddingLimit/index.vue b/src/views/biddingManagemen/biddingLimit/index.vue index 4c508ad..98c5781 100644 --- a/src/views/biddingManagemen/biddingLimit/index.vue +++ b/src/views/biddingManagemen/biddingLimit/index.vue @@ -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` ); }; // 审核 diff --git a/src/views/biddingManagemen/biddingLimit/indexEdit.vue b/src/views/biddingManagemen/biddingLimit/indexEdit.vue index 951d70d..9e268ce 100644 --- a/src/views/biddingManagemen/biddingLimit/indexEdit.vue +++ b/src/views/biddingManagemen/biddingLimit/indexEdit.vue @@ -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; diff --git a/src/views/contract/bidCost/index.vue b/src/views/contract/bidCost/index.vue index 9ef0c55..34bdbb1 100644 --- a/src/views/contract/bidCost/index.vue +++ b/src/views/contract/bidCost/index.vue @@ -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"> @@ -59,8 +59,9 @@ <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="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; @@ -288,7 +290,7 @@ const handleExport = () => { projectId: currentProject.value?.id, sheet: queryForm.value.sheet }, - `限价一览表${queryForm.value.sheet}.xlsx` + `投标成本核算${queryForm.value.sheet}.xlsx` ); }; // 审核 diff --git a/src/views/contract/division/index.vue b/src/views/contract/division/index.vue index 0c7675d..b8e6768 100644 --- a/src/views/contract/division/index.vue +++ b/src/views/contract/division/index.vue @@ -138,9 +138,14 @@ <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) }} </template> </el-table-column> @@ -149,12 +154,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 +337,10 @@ const getVersionNums = async () => { getSheetName(); } else { treeForm.value.versions = ''; - ElMessage({ - message: '获取版本号失败', - type: 'warning' - }); } } } catch (error) { console.log(error); - ElMessage({ - message: '获取版本号失败', - type: 'warning' - }); } }; //获取表名 @@ -356,19 +357,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) => { diff --git a/src/views/contract/limitPrice/index.vue b/src/views/contract/limitPrice/index.vue index 69d7e05..505add3 100644 --- a/src/views/contract/limitPrice/index.vue +++ b/src/views/contract/limitPrice/index.vue @@ -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` ); }; // 审批 diff --git a/src/views/cory/template/indexEdit.vue b/src/views/cory/template/indexEdit.vue index 3f75ed3..bc55402 100644 --- a/src/views/cory/template/indexEdit.vue +++ b/src/views/cory/template/indexEdit.vue @@ -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="请输入变更费用估算" /> diff --git a/src/views/design/appointment/index.vue b/src/views/design/appointment/index.vue index 19bc328..6adfa28 100644 --- a/src/views/design/appointment/index.vue +++ b/src/views/design/appointment/index.vue @@ -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" @@ -221,7 +224,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" @@ -277,7 +281,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" @@ -318,7 +323,7 @@ </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" diff --git a/src/views/design/billofQuantities/index.vue b/src/views/design/billofQuantities/index.vue index fe4e7e2..cec0ad5 100644 --- a/src/views/design/billofQuantities/index.vue +++ b/src/views/design/billofQuantities/index.vue @@ -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'; } diff --git a/src/views/design/billofQuantities/indexEdit0.vue b/src/views/design/billofQuantities/indexEdit0.vue index c09bda7..993d64f 100644 --- a/src/views/design/billofQuantities/indexEdit0.vue +++ b/src/views/design/billofQuantities/indexEdit0.vue @@ -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 diff --git a/src/views/design/billofQuantities/indexEdit3.vue b/src/views/design/billofQuantities/indexEdit3.vue index 2e5eacd..094df79 100644 --- a/src/views/design/billofQuantities/indexEdit3.vue +++ b/src/views/design/billofQuantities/indexEdit3.vue @@ -113,7 +113,7 @@ const approvalRecordRef = ref<InstanceType<typeof ApprovalRecord>>(); //按钮组件 const flowCodeOptions = [ { - value: currentProject.value?.id + '_materialsPlans', + value: currentProject.value?.id + '_equipmentList', label: '物资设备清单审核' } ]; diff --git a/src/views/design/drawing/indexEdit.vue b/src/views/design/drawing/indexEdit.vue index 42b1999..53ab0ec 100644 --- a/src/views/design/drawing/indexEdit.vue +++ b/src/views/design/drawing/indexEdit.vue @@ -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; } diff --git a/src/views/design/received/index.vue b/src/views/design/received/index.vue index b9fc009..3dfa5ef 100644 --- a/src/views/design/received/index.vue +++ b/src/views/design/received/index.vue @@ -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="Upload">导入文件</el-button> + </el-upload> + <el-button + v-if="!form.id || form.status == 'draft'" + type="primary" + style="margin-left: 20px" + icon="Download" + @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(() => { // 先获取当前用户信息,再获取部门用户列表,最后回显表单数据 diff --git a/src/views/design/volumeCatalog/index.vue b/src/views/design/volumeCatalog/index.vue index e231fe4..e6fcbad 100644 --- a/src/views/design/volumeCatalog/index.vue +++ b/src/views/design/volumeCatalog/index.vue @@ -143,6 +143,11 @@ <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> @@ -228,7 +233,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'; @@ -254,6 +260,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: [] @@ -389,7 +396,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 }; @@ -519,7 +532,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(); @@ -629,6 +642,7 @@ const handleAuditInfo = (row) => { // 审核图纸 }; onMounted(() => { + getDesignUserList(); getSpecialtyList(); getList(); }); @@ -639,6 +653,7 @@ const listeningProject = watch( (nid, oid) => { queryParams.value.projectId = nid; form.value.projectId = nid; + getDesignUserList(); getSpecialtyList(); getList(); } diff --git a/src/views/equipment/equipmentGPS.vue b/src/views/equipment/equipmentGPS.vue new file mode 100644 index 0000000..35290b8 --- /dev/null +++ b/src/views/equipment/equipmentGPS.vue @@ -0,0 +1,185 @@ +<template> + <div class="p5" style="width: 100%; height: calc(100vh - 84px)" v-loading="loading"> + <div id="TrajectoryEarth" style="width: 100%; height: 100%"></div> + </div> +</template> + +<script setup name="equipmentGPS"> +import { ref, onMounted, onUnmounted } from 'vue'; +import { ElMessage } from 'element-plus'; +import { useRoute } from 'vue-router'; +import { getFootNote } from '@/api/equipment/index'; + +const route = useRoute(); +const loading = ref(true); +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((point) => { + return Cesium.Cartesian3.fromDegrees(point.locLongitude, point.locLatitude, point.locAltitude || 0); + }); + + const entity = earthInstance.viewer.entities.add({ + polyline: { + positions: positions, + width: 5, + material: Cesium.Color.RED, + clampToGround: true + } + }); + + // 调整视角以适应轨迹 + earthInstance.viewer.flyTo(entity); + } catch (err) { + console.error('渲染轨迹失败:', err); + ElMessage.error('渲染轨迹失败'); + } +}; + +// +onMounted(() => { + createEarth(); +}); + +// +onUnmounted(() => { + if (earthInstance) { + earthInstance.destroy(); + earthInstance = null; + window.Earth3 = null; + } +}); +</script> + +<style></style> diff --git a/src/views/equipment/index.vue b/src/views/equipment/index.vue new file mode 100644 index 0000000..16ded89 --- /dev/null +++ b/src/views/equipment/index.vue @@ -0,0 +1,786 @@ +<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:unbindManmachine', '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="['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)"> </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="deviceName"> + <el-input v-model="bindForm.deviceName" disabled placeholder="设备名称" /> + </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" 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> + + <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) => { + 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?: EquipmentVO) => { + Object.assign(bindForm, { + id: undefined, + projectId: undefined, + userId: undefined, + clientId: row?.clientId, + deviceName: undefined + }); + bindUserFormRef.value?.resetFields(); + userList.value = []; + + const _id = row?.id || ids.value[0]; + try { + const res = await getEquipment(_id); + const equipmentData = res.data; + bindForm.id = equipmentData.id; + bindForm.deviceName = equipmentData.deviceName; + bindForm.clientId = row?.clientId || equipmentData.clientId; + + if (projectList.value.length === 0) { + await getProjects(); + } + + if (equipmentData.projectId) { + bindForm.projectId = equipmentData.projectId; + await getUsersByProjectId(equipmentData.projectId); + } else 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 { + const bindData = { + id: bindForm.id, + projectId: bindForm.projectId, + userId: bindForm.userId, + clientId: bindForm.clientId, + deviceName: bindForm.deviceName + }; + + console.log('提交绑定用户参数:', bindData); + + await bindUser(bindData); + 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 = []; + + 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; + } +}; + +/** 页面挂载时初始化 */ +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> diff --git a/src/views/formalities/formalitiesAreConsolidated/index.vue b/src/views/formalities/formalitiesAreConsolidated/index.vue index 8e80b7f..834f4cd 100644 --- a/src/views/formalities/formalitiesAreConsolidated/index.vue +++ b/src/views/formalities/formalitiesAreConsolidated/index.vue @@ -63,7 +63,7 @@ </el-tooltip> </span> </el-col> - <el-col :span="1.5"> + <!-- <el-col :span="1.5"> <el-button type="success" plain @@ -73,7 +73,7 @@ v-hasPermi="['formalities:formalitiesAreConsolidated:edit']" >修改</el-button > - </el-col> + </el-col> --> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> </el-row> </template> @@ -81,7 +81,7 @@ <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> diff --git a/src/views/materials/materialOutbound/component/outbound.vue b/src/views/materials/materialOutbound/component/outbound.vue new file mode 100644 index 0000000..c182cee --- /dev/null +++ b/src/views/materials/materialOutbound/component/outbound.vue @@ -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> diff --git a/src/views/materials/materialOutbound/index.vue b/src/views/materials/materialOutbound/index.vue new file mode 100644 index 0000000..91a54e3 --- /dev/null +++ b/src/views/materials/materialOutbound/index.vue @@ -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> diff --git a/src/views/materials/materials/index.vue b/src/views/materials/materials/index.vue index 91385e7..1cd6bb8 100644 --- a/src/views/materials/materials/index.vue +++ b/src/views/materials/materials/index.vue @@ -1,13 +1,11 @@ <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="auto"> <el-form-item label="材料名称" prop="materialsName"> - <el-input v-model="queryParams.materialsName" placeholder="请输入材料名称" clearable - @keyup.enter="handleQuery" /> + <el-input v-model="queryParams.materialsName" placeholder="请输入材料名称" clearable @keyup.enter="handleQuery" /> </el-form-item> <el-form-item label="材料提供商" prop="companyId"> <el-select v-model="queryParams.companyId" clearable placeholder="全部"> @@ -27,22 +25,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: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-button type="primary" plain icon="Plus" @click="handleAdd" v-hasPermi="['materials:materials:add']"> 新增 </el-button> </el-col> <right-toolbar v-model:showSearch="showSearch" @queryTable="getList"></right-toolbar> </el-row> @@ -72,64 +55,81 @@ <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> </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-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> @@ -137,8 +137,7 @@ </div> </template> </el-dialog> - <materials-inventory-add-dialog :materials-id="currentMaterialsId" :project-id="currentProject.id" ref="dialogRef" - @submit="getList" /> + <materials-inventory-add-dialog :materials-id="currentMaterialsId" :project-id="currentProject.id" ref="dialogRef" @submit="getList" /> <el-dialog title="材料详情" v-model="showDetailDrawer" width="700px"> <materials-detail-drawer :materials-id="currentMaterialsId" /> </el-dialog> @@ -151,7 +150,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'; @@ -227,14 +226,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.rows.map((company: CompanyVO) => ({ + console.log(companyRes); + + companyOptions.value = companyRes.data.map((company) => ({ value: company.id, - label: company.companyName + label: company.supplierName })); loading.value = false; }; @@ -363,6 +362,7 @@ const listeningProject = watch( (nid, oid) => { queryParams.value.projectId = nid; form.value.projectId = nid; + getCompanyList(); getList(); } ); diff --git a/src/views/materials/materialsEquipment/materialIssue/index.vue b/src/views/materials/materialsEquipment/materialIssue/index.vue index 8d4cdff..8f3e7ae 100644 --- a/src/views/materials/materialsEquipment/materialIssue/index.vue +++ b/src/views/materials/materialsEquipment/materialIssue/index.vue @@ -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(); } ); diff --git a/src/views/materials/materialsEquipment/materialIssue/word/index.vue b/src/views/materials/materialsEquipment/materialIssue/word/index.vue index 7298339..fb44f81 100644 --- a/src/views/materials/materialsEquipment/materialIssue/word/index.vue +++ b/src/views/materials/materialsEquipment/materialIssue/word/index.vue @@ -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> diff --git a/src/views/materials/materialsEquipment/materialReceive/index.vue b/src/views/materials/materialsEquipment/materialReceive/index.vue index 3f1d4c0..90f6d3d 100644 --- a/src/views/materials/materialsEquipment/materialReceive/index.vue +++ b/src/views/materials/materialsEquipment/materialReceive/index.vue @@ -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`, () => {}); } }; diff --git a/src/views/materials/materialsUseRecord/index.vue b/src/views/materials/materialsUseRecord/index.vue index c4a7866..a8da5f4 100644 --- a/src/views/materials/materialsUseRecord/index.vue +++ b/src/views/materials/materialsUseRecord/index.vue @@ -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="4"> + <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="20"> + <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> diff --git a/src/views/materials/overallPlanMaterialSupply/index copy.vue b/src/views/materials/overallPlanMaterialSupply/index copy.vue new file mode 100644 index 0000000..ec294ab --- /dev/null +++ b/src/views/materials/overallPlanMaterialSupply/index copy.vue @@ -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> diff --git a/src/views/materials/overallPlanMaterialSupply/index.vue b/src/views/materials/overallPlanMaterialSupply/index.vue index 4106dc8..8d4c067 100644 --- a/src/views/materials/overallPlanMaterialSupply/index.vue +++ b/src/views/materials/overallPlanMaterialSupply/index.vue @@ -1,6 +1,5 @@ <template> <div class="overall-plan-material-supply"> - <!-- tabPosition="left" --> <el-card shadow="always"> <template #header> <el-row :gutter="10" class="mb8"> @@ -30,19 +29,28 @@ >一键全部保存</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" /> @@ -50,87 +58,95 @@ <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="row.brand" + v-model.lazy="row.brand" :disabled="state.masterData.status != 'draft'" - v-if="!(row.children && row.children.length > 0)" + 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="row.texture" + v-model.lazy="row.texture" :disabled="state.masterData.status != 'draft'" - v-if="!(row.children && row.children.length > 0)" + 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="row.qualityStandard" - v-if="!(row.children && row.children.length > 0)" + 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="row.partUsed" - v-if="!(row.children && row.children.length > 0)" + 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="row.deliveryPoints" - v-if="!(row.children && row.children.length > 0)" + 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.children && row.children.length > 0)" + 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-column label="操作"> - <template #default="{ row }"> - <el-button - :disabled="state.masterData.status == 'waiting' || state.masterData.status == 'finish'" - type="primary" - v-hasPermi="['design:totalsupplyplan:edit']" - @click="editApprovalSheet(row)" - >修改</el-button - > - </template> - </el-table-column> --> </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"> @@ -145,7 +161,6 @@ </el-form-item> </el-col> </el-row> - <!-- 物料属性区域 --> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="材质" prop="texture"> @@ -158,7 +173,6 @@ </el-form-item> </el-col> </el-row> - <!-- 日期与状态区域 --> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="使用部位" prop="partUsed"> @@ -171,7 +185,6 @@ </el-form-item> </el-col> </el-row> - <!-- 其他信息区域 --> <el-row :gutter="20"> <el-col :span="12"> <el-form-item label="预计使用日期" prop="dateService"> @@ -196,7 +209,8 @@ </template> <script setup name="billofQuantities"> -import { ref, reactive, onMounted, computed, getCurrentInstance } from 'vue'; +import { ref, reactive, onMounted, computed, getCurrentInstance, watch, onUnmounted } from 'vue'; +import { ElMessage, ElLoading } from 'element-plus'; import { obtainMasterDataList, totalsupplyplan, @@ -205,13 +219,26 @@ import { 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: [], + tableData: [], // 初始只存放根节点 queryForm: { projectId: currentProject.value?.id, versions: '', @@ -224,9 +251,9 @@ const state = reactive({ sheets: false, list: false }, - // 主id masterData: {} }); + // 表单数据 const formData = reactive({ batchNumber: '', @@ -248,6 +275,7 @@ const formData = reactive({ texture: '', unit: '' }); + // 表单验证规则 const formRules = reactive({ name: [ @@ -264,71 +292,190 @@ const formRules = reactive({ ], 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.sid); + + // 标记子节点是否有子节点 + const formattedChildren = childNodes.map((node) => ({ + ...node, + hasChildren: fullTreeData.value.some((child) => child.pid === node.sid) + })); + + // 缓存子节点到当前行 + row.children = formattedChildren; + resolve(formattedChildren); + } catch (error) { + 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.pid === node.sid); + const formattedChildren = children.map((child) => ({ + ...child, + hasChildren: fullTreeData.value.some((c) => c.pid === child.sid) + })); + 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; - console.log('masterData', masterData); - if (!masterData[0].id) { - console.warn('未获取到有效的主数据ID'); + if (!masterData[0]?.id) { state.tableData = []; + fullTreeData.value = []; return; } state.masterData = masterData[0]; - - // 获取供应计划 - const supplyPlanRes = await totalsupplyplan({ id: masterData[0].id }); - - // 处理结果 - if (supplyPlanRes.list == null) { - // state.tableData = supplyPlanRes.rows || []; - state.tableData = proxy?.handleTree(supplyPlanRes.rows, 'sid', 'pid'); - console.log('state.tableData', state.tableData); - } else { - // 根据实际业务逻辑处理有list的情况 - state.tableData = []; - } + // 获取完整供应计划数据 + const supplyPlanRes = await totalsupplyplan({ id: masterData[0].id, projectId: currentProject.value?.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.sid) + })); + // 初始化可编辑数据映射 + editDataMap.value.clear(); + allData.forEach((item) => { + if (!item.hasChildren) { + // 只关注叶子节点 + editDataMap.value.set(item.id, { ...item }); + } + }); } catch (error) { - console.error('获取主数据列表失败:', error); - // 错误情况下给默认值,避免页面出错 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 || {}; - // 1. 清空原有表单数据 + + // 清空表单并赋值 Object.keys(formData).forEach((key) => { formData[key] = undefined; }); - // 2. 处理日期格式(假设接口返回的是Date对象或ISO字符串) + + // 处理日期格式 const formatDate = (date) => { if (!date) return ''; - // 若为字符串,先转为Date对象 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')}`; }; - // 3. 合并数据到formData(响应式赋值) + Object.assign(formData, { ...detailData, - // 单独处理日期字段,转为表单可识别的字符串格式 compileDate: formatDate(detailData.compileDate), dateService: formatDate(detailData.dateService) }); - console.log('表单数据已更新:', formData); } else { ElMessage.error(`获取详情失败: ${result?.msg || '未知错误'}`); } @@ -339,87 +486,113 @@ async function totalSupplyplanDetail(id) { state.loading.list = false; } } -// 修改 + +/** + * 一键保存修改 + */ const editApprovalSheet = async () => { state.loading.list = true; - await totalSupplyplanBatchEdit(state.tableData); - proxy.$modal.msgSuccess('修改成功'); - - state.loading.list = false; -}; -// 提交表单 -const handleSubmit = async () => { try { - // 表单验证 - await formRef.value.validate(); - // 触发提交事件 - editMaterialSupply(formData); - handleClose(); + // 只提交修改过的数据(从Map中获取) + const modifiedData = Array.from(editDataMap.value.values()); + if (modifiedData.length === 0) { + ElMessage.info('没有数据需要修改'); + return; + } + + await totalSupplyplanBatchEdit(modifiedData); + ElMessage.success('修改成功'); + getMasterDataList(); // 重新加载数据 } catch (error) { - // 验证失败不提交 - console.error('表单验证失败:', error); - return; + ElMessage.error(`保存失败:${error.message}`); + } finally { + state.loading.list = false; } }; -// 修改物资 -function editMaterialSupply(formData) { - materialChangeSupplyplan(formData).then((res) => { - if (res.code === 200) { - ElMessage.success('修改成功'); - getMasterDataList(); - } else { - ElMessage.error('修改失败'); - } - }); -} -const handleExport = async () => { - proxy?.download( - '/design/totalsupplyplan/export', - { - projectId: currentProject.value?.id - }, - `物资供应总计划.xlsx`, - true - ); +/** + * 提交表单 + */ +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; }); - // 重置表单验证状态 - formRef.value?.resetFields(); }; -// 审批 +/** + * 审批 + */ function clickApprovalSheet1() { proxy.$tab.closePage(proxy.$route); proxy.$router.push({ path: `/approval/overallPlanMaterialSupply/indexEdit`, - query: { - id: state.masterData.id, - type: 'update' - } + query: { id: state.masterData.id, type: 'update' } }); } -// 审核流程 + +/** + * 查看流程 + */ function lookApprovalFlow() { proxy.$router.push({ path: `/approval/overallPlanMaterialSupply/indexEdit`, - query: { - id: state.masterData.id, - type: 'view' - } + query: { id: state.masterData.id, type: 'view' } }); } -onMounted(() => { + +/** + * 监听项目变化 + */ +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; diff --git a/src/views/materials/usageMaterials/purchase/index.vue b/src/views/materials/usageMaterials/purchase/index.vue index 4078def..68e19e3 100644 --- a/src/views/materials/usageMaterials/purchase/index.vue +++ b/src/views/materials/usageMaterials/purchase/index.vue @@ -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(); diff --git a/src/views/materials/usageMaterials/purchase/indexSon.vue b/src/views/materials/usageMaterials/purchase/indexSon.vue index 4611d85..5561bf3 100644 --- a/src/views/materials/usageMaterials/purchase/indexSon.vue +++ b/src/views/materials/usageMaterials/purchase/indexSon.vue @@ -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"> diff --git a/src/views/out/monthPlan/index.vue b/src/views/out/monthPlan/index.vue index c426eca..eca4424 100644 --- a/src/views/out/monthPlan/index.vue +++ b/src/views/out/monthPlan/index.vue @@ -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')); @@ -164,7 +164,7 @@ const data = reactive({ valueType: undefined, planAuditStatus: undefined, completeAuditStatus: undefined, - type: '1', + type: '0', params: {} }, rules: { @@ -181,7 +181,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; diff --git a/src/views/out/monthPlan/indexEdit.vue b/src/views/out/monthPlan/indexEdit.vue index 708f793..01cd497 100644 --- a/src/views/out/monthPlan/indexEdit.vue +++ b/src/views/out/monthPlan/indexEdit.vue @@ -175,7 +175,7 @@ const getInfo = () => { form.value = res.data as any; console.log('🚀 ~ getInfo ~ form.value:', form.value[0].projectId); - + form.value[0].mid = form.value[0].id; form.value[0].id = form.value[0].projectId + '_' + form.value[0].planMonth; loading.value = false; buttonLoading.value = false; @@ -231,13 +231,15 @@ const approvalVerifyOpen = async () => { // 图纸上传成功之后 开始提交 const submit = async (status, data) => { form.value = data; + console.log(form.value); + if (status === 'draft') { buttonLoading.value = false; proxy?.$modal.msgSuccess('暂存成功'); proxy.$tab.closePage(proxy.$route); proxy.$router.go(-1); } else { - const res = await isSubmit(data[1]?.id); + const res = await isSubmit(data[0]?.mid); if (!res.data) { proxy?.$modal.msgError('三种计划产值必须填写'); diff --git a/src/views/out/outDesignTable/index.vue b/src/views/out/outDesignTable/index.vue index cdb6652..58bef11 100644 --- a/src/views/out/outDesignTable/index.vue +++ b/src/views/out/outDesignTable/index.vue @@ -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; diff --git a/src/views/progress/plan/component/createDailyRate.vue b/src/views/progress/plan/component/createDailyRate.vue index 6546c08..bbbd05d 100644 --- a/src/views/progress/plan/component/createDailyRate.vue +++ b/src/views/progress/plan/component/createDailyRate.vue @@ -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 || []; diff --git a/src/views/progress/progressCategory/index.vue b/src/views/progress/progressCategory/index.vue index 0344712..dc775dd 100644 --- a/src/views/progress/progressCategory/index.vue +++ b/src/views/progress/progressCategory/index.vue @@ -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(); } diff --git a/src/views/project/landTransfer/BasicData/enterRoad/index.vue b/src/views/project/landTransfer/BasicData/enterRoad/index.vue index a553d98..10a765e 100644 --- a/src/views/project/landTransfer/BasicData/enterRoad/index.vue +++ b/src/views/project/landTransfer/BasicData/enterRoad/index.vue @@ -27,9 +27,9 @@ <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-col :span="1.5"> <el-button type="success" plain icon="Plus" @click="downloadTemplate" v-hasPermi="['land:enterRoad:import']">模板下载</el-button> - </el-col> + </el-col> --> <el-col :span="1.5"> <el-upload ref="uploadRef" diff --git a/src/views/project/landTransfer/BasicData/landBlock/index.vue b/src/views/project/landTransfer/BasicData/landBlock/index.vue index 8c215a0..73ca85b 100644 --- a/src/views/project/landTransfer/BasicData/landBlock/index.vue +++ b/src/views/project/landTransfer/BasicData/landBlock/index.vue @@ -36,9 +36,9 @@ <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-col :span="1.5"> <el-button type="success" plain icon="Plus" @click="downloadTemplate" v-hasPermi="['land:enterRoad:import']">模板下载</el-button> - </el-col> + </el-col> --> <el-col :span="1.5"> <el-upload ref="uploadRef" class="upload-demo" :http-request="handleImport" :show-file-list="false"> <template #trigger> @@ -78,6 +78,7 @@ </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"> @@ -107,20 +108,22 @@ </div> </template> </el-dialog> - <!-- 关联方阵弹窗(核心修改区域) --> + + <!-- 关联方阵弹窗(核心修复区域) --> <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> - <!-- 方阵表单:绑定unitBoList的索引,实现动态校验 --> + <!-- 修复1:表单模型绑定formM(根模型),确保嵌套字段校验生效 --> <el-form ref="landBlockFormMatrixRef" :model="formM" label-width="100px"> - <el-row v-for="(item, i) of unitBoList" :key="i" class="mb-4"> - <!-- 方阵选择:必填校验 --> + <!-- 修复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: 2, message: '请选择完整的方阵层级', trigger: 'change' } + { type: 'array', min: 1, message: '请选择完整的方阵', trigger: 'change' } ]" > <el-cascader @@ -133,7 +136,7 @@ /> </el-form-item> </el-col> - <!-- 所属工区:必填校验 --> + <!-- 所属工区:保留原有规则 --> <el-col :span="8"> <el-form-item label="所属工区" @@ -143,7 +146,7 @@ <el-input v-model="item.unitProjectArea" placeholder="请输入所属工区" /> </el-form-item> </el-col> - <!-- 方阵状态:必填校验 --> + <!-- 方阵状态:保留原有规则 --> <el-col :span="6"> <el-form-item label="方阵状态" @@ -153,8 +156,15 @@ <el-input v-model="item.unitProjectStatus" placeholder="请输入方阵状态" /> </el-form-item> </el-col> + <!-- 删除按钮:禁用逻辑优化(至少保留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)"></el-button> + <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> @@ -181,21 +191,35 @@ import { } 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 } from 'vue'; -import { ElFormInstance, ElMessage } from 'element-plus'; +import { getCurrentInstance, ComponentInternalInstance, onMounted, onUnmounted, watch, reactive, ref, toRefs, computed } from 'vue'; +import { ElFormInstance } from 'element-plus'; -// 类型定义补充(避免any) +// 类型定义补充 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; const userStore = useUserStoreHook(); @@ -203,14 +227,17 @@ const currentProject = computed(() => userStore.selectedProject); // 响应式数据 const landBlockList = ref<LandBlockVO[]>([]); -const fangzhenList = ref<any[]>([]); // 方阵列表(实际项目建议定义具体类型) +const fangzhenList = ref<FangzhenOption[]>([]); const buttonLoading = ref(false); const loading = ref(true); const showSearch = ref(true); -const uploadRef = ref<any>(null); // upload组件ref +const uploadRef = ref<any>(null); -// 方阵表单数据(核心修改:初始值为空,避免默认填充无效数据) -const unitBoList = ref<UnitBoItem[]>([{ unitProjectArea: '', unitProjectStatus: '', unitProjectId: [] }]); +// 方阵表单模型(核心修复:使用reactive并显式声明结构) +const formM = ref<MatrixForm>({ + landId: undefined, + unitBoList: [{ unitProjectArea: '', unitProjectStatus: '', unitProjectId: [] }] +}); // 表格选择相关 const ids = ref<Array<string | number>>([]); @@ -243,7 +270,6 @@ const initFormData: LandBlockForm = { // 核心数据(含表单规则) const data = reactive({ form: { ...initFormData }, - formM: { landId: undefined }, // 方阵关联表单(仅存地块ID) queryParams: { pageNum: 1, pageSize: 10, @@ -256,7 +282,7 @@ const data = reactive({ farmerCount: undefined, params: {} }, - // 地块表单规则(原有规则保留) + // 地块表单规则 rules: { id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }], projectId: [{ required: true, message: '项目ID不能为空', trigger: 'blur' }], @@ -265,7 +291,7 @@ const data = reactive({ } }); -const { queryParams, form, rules, formM } = toRefs(data); +const { queryParams, form, rules } = toRefs(data); /** 查询地块列表 */ const getList = async () => { @@ -381,10 +407,10 @@ const getfangzhenList = async () => { loading.value = true; try { const res = await subMatrix(currentProject.value.id); - // 处理方阵数据(级联选择需父子结构,此处保留原有逻辑) + // 处理方阵数据(级联选择需父子结构) res.data.forEach((item: any) => { item.children?.forEach((item2: any) => { - item2.matrixId = `${item2.name}_${item2.matrixId}`; // 拼接名称+ID,便于后续拆分 + item2.matrixId = `${item2.name}_${item2.matrixId}`; }); }); fangzhenList.value = res.data; @@ -395,55 +421,62 @@ const getfangzhenList = async () => { } }; -/** 关联方阵(核心修改:打开弹窗前强制重置表单) */ +/** 关联方阵 */ const handleView = async (row: LandBlockVO) => { if (!row?.id) return proxy?.$modal.msgWarning('请选择有效的地块'); - - // 1. 重置方阵表单(清空历史数据) + console.log('🚀 ~ handleView ~ row:', row); + // 重置方阵表单 resetMatrix(); - // 2. 绑定当前地块ID + // 绑定当前地块ID formM.value.landId = row.id; - // 3. 打开弹窗 + // 打开弹窗 dialogMatrix.visible = true; + dialogMatrix.title = `关联方阵(地块:${row.landName || row.landCode})`; }; /** 新增方阵表单项 */ const addUnitBoItem = () => { - unitBoList.value.push({ + formM.value.unitBoList.push({ unitProjectArea: '', unitProjectStatus: '', - unitProjectId: [] // 级联选择初始为空数组 + unitProjectId: [] }); + // 重置校验状态 + landBlockFormMatrixRef.value?.clearValidate(); }; /** 删除方阵表单项 */ const removeUnitBoItem = (index: number) => { - if (unitBoList.value.length <= 1) { + if (formM.value.unitBoList.length <= 1) { return proxy?.$modal.msgWarning('至少保留一项方阵配置'); } - unitBoList.value.splice(index, 1); - // 重置表单校验状态(避免删除后残留校验提示) + formM.value.unitBoList.splice(index, 1); landBlockFormMatrixRef.value?.clearValidate(); }; -/** 提交方阵关联表单 */ +/** 提交方阵关联表单(核心修复:数据处理逻辑) */ const submitFormMatrix = () => { landBlockFormMatrixRef.value?.validate(async (valid: boolean) => { if (!valid) return; if (!formM.value.landId) return proxy?.$modal.msgWarning('地块ID异常,请重新选择地块'); try { - // 处理方阵数据(拆分名称+ID) - const unitBoListParams = unitBoList.value.map((item) => { - const [unitProjectName, unitProjectId] = item.unitProjectId[1]?.split('_') || []; - if (!unitProjectId) throw new Error('方阵ID解析失败,请重新选择方阵'); + // 处理方阵数据(修复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, // 纯ID(后端需要) - unitProjectName: unitProjectName // 名称(可选,用于显示) + unitProjectId: unitProjectId, + unitProjectName: unitProjectName }; }); @@ -456,7 +489,7 @@ const submitFormMatrix = () => { if (res.code === 200) { proxy?.$modal.msgSuccess('关联方阵成功'); dialogMatrix.visible = false; - await getList(); // 刷新地块列表 + await getList(); } else { proxy?.$modal.msgError(res.msg || '关联失败'); } @@ -472,17 +505,14 @@ const cancelMatrix = () => { dialogMatrix.visible = false; }; -/** 方阵表单重置(核心修改:清空所有数据+重置校验) */ +/** 方阵表单重置 */ const resetMatrix = () => { - // 1. 清空地块ID - formM.value.landId = undefined; - // 2. 重置方阵列表(仅保留一个空项) - unitBoList.value = [{ unitProjectArea: '', unitProjectStatus: '', unitProjectId: [] }]; - // 3. 重置表单校验状态 if (landBlockFormMatrixRef.value) { landBlockFormMatrixRef.value.resetFields(); landBlockFormMatrixRef.value.clearValidate(); } + formM.value.landId = undefined; + formM.value.unitBoList = [{ unitProjectArea: '', unitProjectStatus: '', unitProjectId: [] }]; }; /** 监听项目变化,刷新数据 */ @@ -491,11 +521,11 @@ const listeningProject = watch( (newId) => { if (newId) { queryParams.value.projectId = newId; - getfangzhenList(); // 刷新方阵列表 - getList(); // 刷新地块列表 + getfangzhenList(); + getList(); } }, - { immediate: true } // 初始加载时执行一次 + { immediate: true } ); /** 导入Excel */ @@ -524,36 +554,34 @@ const handleImport = (options: { file: File }) => { loading.value = false; }); }; + +/** 导出模板 */ const exportFile = () => { - // 导出模版文件 try { - // 创建a标签 const link = document.createElement('a'); - // 设置PDF文件路径 - 相对于public目录 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'; // 模板路径(需确保public目录下存在该文件) - link.download = '地块信息导入模板.xlsx'; // 下载后文件名 + link.href = '/landBlock.xlsx'; + link.download = '地块信息导入模板.xlsx'; document.body.appendChild(link); - link.click(); // 触发下载 + link.click(); } catch (err) { proxy?.$modal.msgError('模板下载失败,请重试'); } finally { - document.body.removeChild(link); // 清理DOM + const link = document.querySelector('a[download="地块信息导入模板.xlsx"]'); + if (link) document.body.removeChild(link); } }; diff --git a/src/views/project/landTransfer/BusinessLedger/landTransferLedger/index.vue b/src/views/project/landTransfer/BusinessLedger/landTransferLedger/index.vue index 28839b3..309b810 100644 --- a/src/views/project/landTransfer/BusinessLedger/landTransferLedger/index.vue +++ b/src/views/project/landTransfer/BusinessLedger/landTransferLedger/index.vue @@ -4,7 +4,7 @@ <div> <div> <span>设计面积</span> - <span>{{ detailInfo.designArea }} 亩</span> + <span>{{ detailInfo.areaSum }} 亩</span> </div> <el-icon :size="50" color="#3176ff"> <Postcard /> @@ -13,7 +13,7 @@ <div> <div> <span>已流转面积</span> - <span>{{ detailInfo.transferAea }} 亩</span> + <span>{{ detailInfo.transferAreaSum }} 亩</span> </div> <el-icon :size="50" color="#3176ff"> <Postcard /> @@ -22,7 +22,7 @@ <div> <div> <span>租金</span> - <span>{{ detailInfo.landRent / 1000 }} 万元</span> + <span>{{ detailInfo.rentSum / 1000 }} 万元</span> </div> <el-icon :size="50" color="#3176ff"> <Postcard /> @@ -42,6 +42,7 @@ <el-select v-model="queryParams.transferStatus" placeholder="请选择流转台账状态" clearable> <el-option label="待流转" :value="0"></el-option> <el-option label="已流转" :value="1"></el-option> + <el-option label="不流转" :value="2"></el-option> </el-select> </el-form-item> <el-form-item label="责任人" prop="responsiblePerson"> @@ -74,18 +75,25 @@ <el-table-column label="责任人" align="center" prop="responsiblePerson" /> <el-table-column label="预计完成时间" align="center" prop="expectedFinishDate" width="180"> </el-table-column> <el-table-column label="流转状态" align="center" prop="transferStatusName" /> - <el-table-column label="已流转面积(亩)" align="center" prop="transferAea" width="180" /> - <el-table-column label="流转比例(%)" align="center" prop="transferRatio" width="180" /> - <el-table-column label="土地租金(元)" align="center" prop="landRent" width="180" /> - <el-table-column label="青苗赔偿(元)" align="center" prop="seedlingCompensation" width="180" /> - <el-table-column label="总金额(元)" align="center" prop="totalAmount" width="150" /> - <el-table-column label="状态说明" align="center" prop="statusDescription" /> - <el-table-column label="问题总结" align="center" prop="issueSummary" /> - <el-table-column label="下一步策略" align="center" prop="nextStrategy" width="180" /> + <el-table-column label="已流转面积(亩)" align="center" prop="transferArea" width="180" /> + <el-table-column label="不流转数据" align="center" prop="noTrans" width="180" /> + <el-table-column label="未流转数据" align="center" prop="noTransferAea" width="180" /> + <el-table-column label="流转比例(%)" align="center" width="180"> + <template #default="scope"> + {{ scope.row.transferArea && scope.row.designArea ? ((scope.row.transferArea / scope.row.designArea) * 100).toFixed(2) : '0.00' }} + </template> + </el-table-column> + <el-table-column label="土地租金(元)" align="center" prop="landRentAll" width="180" /> + <el-table-column label="青苗赔偿(元)" align="center" prop="seedlingCompensationAll" width="180" /> + <el-table-column label="总金额(元)" align="center" prop="totalAmountAll" width="150" /> + <el-table-column label="操作" align="center" fixed="right" width="200"> <template #default="scope"> - <el-tooltip content="修改" placement="top"> - <el-button link type="primary" @click="handleUpdate(scope.row)" v-hasPermi="['land:landTransferLedger:edit']">编辑</el-button> + <!-- 查看子项按钮 --> + <el-tooltip content="查看子项" placement="top"> + <el-button link type="primary" @click="handleViewSons(scope.row)" v-hasPermi="['land:landTransferLedger:childrenList']"> + 查看子项 + </el-button> </el-tooltip> <el-tooltip content="删除" placement="top"> <el-button link type="primary" @click="handleDelete(scope.row)" v-hasPermi="['land:landTransferLedger:remove']">删除</el-button> @@ -95,111 +103,413 @@ </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="900px" append-to-body> + + <!-- 子项列表弹窗 --> + <el-dialog draggable title="子项列表" v-model="sonDialog.visible" width="1300px" append-to-body> + <el-card shadow="never"> + <!-- 子项列表统计展示区域 --> + <div class="summary-box"> + <div class="summary-item design-area"> + <div class="summary-content"> + <span class="summary-label">设计面积</span> + <span class="summary-value">{{ sonSummaryInfo.parentDesignArea }} 亩</span> + </div> + <el-icon class="summary-icon" :size="50" color="#3176ff"> + <Postcard /> + </el-icon> + </div> + <div class="summary-item transfer-area"> + <div class="summary-content"> + <span class="summary-label">已流转面积</span> + <span class="summary-value">{{ sonSummaryInfo.totalTransferArea }} 亩</span> + </div> + <el-icon class="summary-icon" :size="50" color="#3176ff"> + <Postcard /> + </el-icon> + </div> + <div class="summary-item non-transfer-area"> + <div class="summary-content"> + <span class="summary-label">不流转面积</span> + <span class="summary-value">{{ sonSummaryInfo.totalNonTransferArea }} 亩</span> + </div> + <el-icon class="summary-icon" :size="50" color="#3176ff"> + <Postcard /> + </el-icon> + </div> + <div class="summary-item remaining-area"> + <div class="summary-content"> + <span class="summary-label">未流转面积</span> + <span class="summary-value">{{ sonSummaryInfo.remainingArea }} 亩</span> + </div> + <el-icon class="summary-icon" :size="50" color="#3176ff"> + <Postcard /> + </el-icon> + </div> + </div> + <template #header> + <el-row :gutter="10" class="mb8"> + <el-col :span="1.5"> + <el-button type="primary" plain icon="Plus" @click="handleAddSon" v-hasPermi="['land:landTransferLedger:childrenAdd']"> + 新增子项 + </el-button> + </el-col> + </el-row> + </template> + + <!-- 已流转子项 --> + <div v-if="sonTransferLedgerList1.length > 0"> + <h4 style="margin-bottom: 10px; color: #3176ff">已流转</h4> + <el-table v-loading="sonLoading" :data="sonTransferLedgerList1" @selection-change="handleSonSelectionChange"> + <el-table-column type="index" label="序号" width="60" align="center" /> + <el-table-column label="土地类型" align="center" prop="landTypeName" /> + <el-table-column label="地块" align="center" prop="landName" /> + <el-table-column label="设计面积(亩)" align="center" prop="designArea" width="180" /> + <el-table-column label="责任人" align="center" prop="responsiblePerson" /> + <el-table-column label="流转状态" align="center" prop="transferStatusName" /> + <el-table-column label="已流转面积(亩)" align="center" prop="areaValue" width="180" /> + <el-table-column label="流转比例(%)" align="center" prop="transferRatio" width="180" /> + <el-table-column label="土地租金(元)" align="center" prop="landRent" width="180" /> + <el-table-column label="青苗赔偿(元)" align="center" prop="seedlingCompensation" width="180" /> + <el-table-column label="总金额(元)" align="center" prop="totalAmount" width="150" /> + <el-table-column label="状态说明" align="center" prop="statusDescription" /> + <el-table-column label="问题总结" align="center" prop="issueSummary" /> + <el-table-column label="下一步策略" align="center" prop="nextStrategy" width="180" /> + </el-table> + </div> + + <!-- 不流转子项 --> + <div v-if="sonTransferLedgerList2.length > 0"> + <h4 style="margin-top: 20px; margin-bottom: 10px; color: #ff6b6b">不流转</h4> + <el-table v-loading="sonLoading" :data="sonTransferLedgerList2" @selection-change="handleSonSelectionChange"> + <el-table-column type="index" label="序号" width="60" align="center" /> + <el-table-column label="土地类型" align="center" prop="landTypeName" /> + <el-table-column label="地块" align="center" prop="landName" /> + <el-table-column label="设计面积(亩)" align="center" prop="designArea" width="180" /> + <el-table-column label="责任人" align="center" prop="responsiblePerson" /> + <el-table-column label="流转状态" align="center" prop="transferStatusName" /> + <el-table-column label="不流转面积(亩)" align="center" prop="areaValue" width="180" /> + <el-table-column label="不签合同面积(亩)" align="center" prop="noContractArea" width="180" /> + <el-table-column label="不测量面积(亩)" align="center" prop="noSurveyArea" width="180" /> + <el-table-column label="不签合同原因" align="center" prop="noContractReason" width="180" /> + <el-table-column label="不流转原因" align="center" prop="nonTransferReason" width="180" /> + </el-table> + </div> + + <!-- 未流转子项 --> + <div v-if="sonTransferLedgerList0.length > 0"> + <h4 style="margin-top: 20px; margin-bottom: 10px; color: #ffc107">未流转子项</h4> + <el-table v-loading="sonLoading" :data="sonTransferLedgerList0" @selection-change="handleSonSelectionChange"> + <el-table-column type="index" label="序号" width="60" align="center" /> + <el-table-column label="土地类型" align="center" prop="landTypeName" /> + <el-table-column label="地块" align="center" prop="landName" /> + <el-table-column label="设计面积(亩)" align="center" prop="designArea" width="180" /> + <el-table-column label="责任人" align="center" prop="responsiblePerson" /> + <el-table-column label="流转状态" align="center" prop="transferStatusName" /> + </el-table> + </div> + + <!-- 无数据提示 --> + <div v-if="sonLandTransferLedgerList.length === 0 && !sonLoading" class="text-center py-10"> + <el-empty description="暂无子项数据" /> + </div> + </el-card> + <template #footer> + <div class="dialog-footer"> + <el-button @click="handleCloseSonDialog">关闭</el-button> + </div> + </template> + </el-dialog> + + <!-- 新增子项对话框 --> + <el-dialog draggable :title="sonFormDialog.title" v-model="sonFormDialog.visible" width="600px" append-to-body> + <el-form ref="sonLandTransferLedgerFormRef" :model="sonForm" :rules="sonRules" label-width="120px"> + <el-row> + <el-col :span="24"> + <el-form-item label="对应地块" prop="landBlockId"> + <el-select v-model="sonForm.landBlockId" clearable placeholder="请选择对应地块" @change="handleLandBlockChange" :disabled="true"> + <el-option v-for="item in landBlockList" :key="item.id" :label="item.landName" :value="item.id" /> + </el-select> + </el-form-item> + </el-col> + + <!-- 当enterRoadList有数据时才显示进场道路选择框 --> + <el-col :span="24" v-if="enterRoadList.length > 0"> + <el-form-item label="进场道路" prop="enterRoadId"> + <el-select v-model="sonForm.enterRoadId" clearable placeholder="请选择进场道路" :disabled="true"> + <el-option v-for="item in enterRoadList" :key="item.id" :label="item.roadName" :value="item.id" /> + </el-select> + </el-form-item> + </el-col> + + <el-col :span="24"> + <el-form-item label="土地类型" prop="landType"> + <el-select v-model="sonForm.landType" placeholder="请选择土地类型" clearable :disabled="true"> + <el-option v-for="dict in land_type" :key="dict.value" :label="dict.label" :value="dict.value" /> + </el-select> + </el-form-item> + </el-col> + + <el-col :span="24"> + <el-form-item label="流转台账状态" prop="transferStatus"> + <el-select v-model="sonForm.transferStatus" placeholder="请选择流转台账状态" clearable @change="calcSonTransferRatio"> + <el-option + v-for="dict in land_transfer_status.filter((item) => item.value !== '0')" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> + </el-select> + </el-form-item> + </el-col> + + <el-col :span="24"> + <el-form-item label="设计面积(亩)" prop="designArea"> + <el-input type="number" v-model="sonForm.designArea" placeholder="请输入设计面积" @input="calcSonTransferRatio" :disabled="true" /> + </el-form-item> + </el-col> + + <el-col :span="24"> + <el-form-item label="责任人" prop="responsiblePerson"> + <el-input v-model="sonForm.responsiblePerson" placeholder="请输入责任人" :disabled="true" /> + </el-form-item> + </el-col> + + <el-col :span="24" v-if="sonForm.transferStatus != '2'"> + <el-form-item label="预计完成日期" prop="expectedFinishDate"> + <el-date-picker + clearable + v-model="sonForm.expectedFinishDate" + type="date" + value-format="YYYY-MM-DD" + placeholder="请选择预计完成时间" + :disabled="true" + > + </el-date-picker> + </el-form-item> + </el-col> + + <el-col :span="24" v-if="sonForm.transferStatus == '1'"> + <el-form-item label="已流转面积(亩)" prop="areaValue"> + <el-input v-model="sonForm.areaValue" type="number" placeholder="请输入已流转面积" @input="calcSonTransferRatio" /> + <div style="color: #ff4d4f; font-size: 12px; margin-top: 4px"> + {{ sonForm.areaValue && sonForm.transferStatus == '1' ? `提示:已流转面积不能超过设计面积 ${sonForm.designArea} 亩` : '' }} + </div> + </el-form-item> + </el-col> + + <!-- 流转比例:改为禁用输入,自动计算展示 --> + <el-col :span="24" v-if="sonForm.transferStatus == '1'"> + <el-form-item label="流转比例(%)" prop="transferRatio"> + <el-input v-model="sonForm.transferRatio" type="text" :disabled="true" placeholder="自动计算中..." /> + </el-form-item> + </el-col> + + <el-col :span="24" v-if="sonForm.transferStatus == '1'"> + <el-form-item label="土地租金(元)" prop="landRent"> + <el-input type="number" v-model="sonForm.landRent" placeholder="请输入土地租金" /> + </el-form-item> + </el-col> + + <el-col :span="24" v-if="sonForm.transferStatus == '1'"> + <el-form-item label="青苗赔偿(元)" prop="seedlingCompensation"> + <el-input v-model="sonForm.seedlingCompensation" type="number" placeholder="请输入青苗赔偿" /> + </el-form-item> + </el-col> + + <el-col :span="24" v-if="sonForm.transferStatus == '1'"> + <el-form-item label="总金额(元)" prop="totalAmount"> + <el-input type="number" v-model="sonForm.totalAmount" placeholder="请输入总金额" /> + </el-form-item> + </el-col> + <el-col :span="24" v-if="sonForm.transferStatus == '2'"> + <el-form-item label="不流转面积(亩)" prop="areaValue"> + <el-input v-model="sonForm.areaValue" type="number" placeholder="请输入不流转面积" /> + <div style="color: #ff4d4f; font-size: 12px; margin-top: 4px"> + {{ sonForm.areaValue && sonForm.transferStatus == '2' ? `提示:不流转面积不能超过设计面积 ${sonForm.designArea} 亩` : '' }} + </div> + </el-form-item> + </el-col> + <el-col :span="24" v-if="sonForm.transferStatus == '2'"> + <el-form-item label="不签合同面积(亩)" prop="noContractArea"> + <el-input v-model="sonForm.noContractArea" type="number" placeholder="请输入不签合同面积" /> + </el-form-item> + </el-col> + <el-col :span="24" v-if="sonForm.transferStatus == '2'"> + <el-form-item label="不测量面积(亩)" prop="noSurveyArea"> + <el-input v-model="sonForm.noSurveyArea" type="number" placeholder="请输入不测量面积" /> + </el-form-item> + </el-col> + + <el-col :span="24" v-if="sonForm.transferStatus == '2'"> + <el-form-item label="不签合同原因" prop="noContractReason"> + <el-input v-model="sonForm.noContractReason" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-col> + + <el-col :span="24" v-if="sonForm.transferStatus == '2'"> + <el-form-item label="不流转原因" prop="nonTransferReason"> + <el-input v-model="sonForm.nonTransferReason" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-col> + + <el-col :span="24" v-if="sonForm.transferStatus == '1'"> + <el-form-item label="状态说明" prop="statusDescription"> + <el-input v-model="sonForm.statusDescription" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-col> + + <el-col :span="24" v-if="sonForm.transferStatus == '1'"> + <el-form-item label="问题总结" prop="issueSummary"> + <el-input v-model="sonForm.issueSummary" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-col> + + <el-col :span="24" v-if="sonForm.transferStatus == '1'"> + <el-form-item label="下一步策略" prop="nextStrategy"> + <el-input v-model="sonForm.nextStrategy" type="textarea" placeholder="请输入内容" /> + </el-form-item> + </el-col> + </el-row> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button v-hasPermi="['land:landTransferLedger:addSon']" :loading="sonButtonLoading" type="primary" @click="submitSonForm" + >确 定</el-button + > + <el-button @click="cancelSonForm">取 消</el-button> + </div> + </template> + </el-dialog> + + <!-- 新增主项对话框 --> + <el-dialog draggable :title="dialog.title" v-model="dialog.visible" width="600px" append-to-body> <el-form ref="landTransferLedgerFormRef" :model="form" :rules="rules" label-width="120px"> <el-row> - <el-col :span="12"> + <el-col :span="24"> <el-form-item label="对应地块" prop="landBlockId"> <el-select v-model="form.landBlockId" clearable placeholder="请选择对应地块" @change="handleLandBlockChange"> <el-option v-for="item in landBlockList" :key="item.id" :label="item.landName" :value="item.id" /> </el-select> </el-form-item> </el-col> - <el-col :span="12"> + + <!-- 当enterRoadList有数据时才显示进场道路选择框 --> + <el-col :span="24" v-if="enterRoadList.length > 0"> <el-form-item label="进场道路" prop="enterRoadId"> - <el-select v-model="form.enterRoadId" clearable placeholder="请选择对应地块"> + <el-select v-model="form.enterRoadId" clearable placeholder="请选择进场道路"> <el-option v-for="item in enterRoadList" :key="item.id" :label="item.roadName" :value="item.id" /> </el-select> </el-form-item> </el-col> - <el-col :span="12"> + + <el-col :span="24"> <el-form-item label="土地类型" prop="landType"> <el-select v-model="form.landType" placeholder="请选择土地类型" clearable> <el-option v-for="dict in land_type" :key="dict.value" :label="dict.label" :value="dict.value" /> </el-select> </el-form-item> </el-col> - <el-col :span="12"> + + <el-col :span="24"> <el-form-item label="流转台账状态" prop="transferStatus"> - <el-select v-model="form.transferStatus" :disabled="!form.id" placeholder="请选择流转台账状态" clearable @change="calcTransferRatio"> - <el-option v-for="dict in land_transfer_status" :key="dict.value" :label="dict.label" :value="dict.value" /> + <el-select v-model="form.transferStatus" placeholder="请选择流转台账状态" clearable @change="calcTransferRatio"> + <el-option + v-for="dict in land_transfer_status.filter((item) => item.value === '0')" + :key="dict.value" + :label="dict.label" + :value="dict.value" + /> </el-select> </el-form-item> </el-col> - <el-col :span="12"> + + <el-col :span="24"> <el-form-item label="设计面积(亩)" prop="designArea"> <el-input type="number" v-model="form.designArea" placeholder="请输入设计面积" @input="calcTransferRatio" /> </el-form-item> </el-col> - <el-col :span="12"> + + <el-col :span="24"> <el-form-item label="责任人" prop="responsiblePerson"> <el-input v-model="form.responsiblePerson" placeholder="请输入责任人" /> </el-form-item> </el-col> - <el-col v-if="form.transferStatus != '2'" :span="12"> + + <el-col :span="24" v-if="form.transferStatus != '2'"> <el-form-item label="预计完成日期" prop="expectedFinishDate"> <el-date-picker clearable v-model="form.expectedFinishDate" type="date" value-format="YYYY-MM-DD" placeholder="请选择预计完成时间"> </el-date-picker> </el-form-item> </el-col> - <el-col v-if="form.transferStatus == '1'" :span="12"> + + <el-col :span="24" v-if="form.transferStatus == '1'"> <el-form-item label="已流转面积(亩)" prop="transferAea"> <el-input v-model="form.transferAea" type="number" placeholder="请输入已流转面积" @input="calcTransferRatio" /> </el-form-item> </el-col> + <!-- 流转比例:改为禁用输入,自动计算展示 --> - <el-col v-if="form.transferStatus == '1'" :span="12"> + <el-col :span="24" v-if="form.transferStatus == '1'"> <el-form-item label="流转比例(%)" prop="transferRatio"> <el-input v-model="form.transferRatio" type="text" :disabled="true" placeholder="自动计算中..." /> </el-form-item> </el-col> - <el-col v-if="form.transferStatus == '1'" :span="12"> + + <el-col :span="24" v-if="form.transferStatus == '1'"> <el-form-item label="土地租金(元)" prop="landRent"> <el-input type="number" v-model="form.landRent" placeholder="请输入土地租金" /> </el-form-item> </el-col> - <el-col v-if="form.transferStatus == '1'" :span="12"> + + <el-col :span="24" v-if="form.transferStatus == '1'"> <el-form-item label="青苗赔偿(元)" prop="seedlingCompensation"> <el-input v-model="form.seedlingCompensation" type="number" placeholder="请输入青苗赔偿" /> </el-form-item> </el-col> - <el-col v-if="form.transferStatus == '1'" :span="12"> + + <el-col :span="24" v-if="form.transferStatus == '1'"> <el-form-item label="总金额(元)" prop="totalAmount"> <el-input type="number" v-model="form.totalAmount" placeholder="请输入总金额" /> </el-form-item> </el-col> - <el-col v-if="form.transferStatus == '2'" :span="12"> + + <el-col :span="24" v-if="form.transferStatus == '2'"> <el-form-item label="不签合同面积(亩)" prop="noContractArea"> <el-input v-model="form.noContractArea" type="number" placeholder="请输入不签合同面积" /> </el-form-item> </el-col> - <el-col v-if="form.transferStatus == '2'" :span="12"> + + <el-col :span="24" v-if="form.transferStatus == '2'"> <el-form-item label="不测量面积(亩)" prop="noSurveyArea"> <el-input v-model="form.noSurveyArea" type="number" placeholder="请输入不测量面积" /> </el-form-item> </el-col> - <el-col v-if="form.transferStatus == '2'" :span="24"> + + <el-col :span="24" v-if="form.transferStatus == '2'"> <el-form-item label="不签合同原因" prop="noContractReason"> <el-input v-model="form.noContractReason" type="textarea" placeholder="请输入内容" /> </el-form-item> </el-col> - <el-col v-if="form.transferStatus == '2'" :span="24"> + + <el-col :span="24" v-if="form.transferStatus == '2'"> <el-form-item label="不流转原因" prop="nonTransferReason"> <el-input v-model="form.nonTransferReason" type="textarea" placeholder="请输入内容" /> </el-form-item> </el-col> - <el-col v-if="form.transferStatus == '1'" :span="24"> + + <el-col :span="24" v-if="form.transferStatus == '1'"> <el-form-item label="状态说明" prop="statusDescription"> <el-input v-model="form.statusDescription" type="textarea" placeholder="请输入内容" /> </el-form-item> </el-col> - <el-col v-if="form.transferStatus == '1'" :span="24"> + + <el-col :span="24" v-if="form.transferStatus == '1'"> <el-form-item label="问题总结" prop="issueSummary"> <el-input v-model="form.issueSummary" type="textarea" placeholder="请输入内容" /> </el-form-item> </el-col> - <el-col v-if="form.transferStatus == '1'" :span="24"> + + <el-col :span="24" v-if="form.transferStatus == '1'"> <el-form-item label="下一步策略" prop="nextStrategy"> <el-input v-model="form.nextStrategy" type="textarea" placeholder="请输入内容" /> </el-form-item> @@ -222,8 +532,11 @@ import { getLandTransferLedger, delLandTransferLedger, addLandTransferLedger, - updateLandTransferLedger, - landTransferLedgerCount + landTransferLedgerCount, + listallCountValue, + // 新增子项相关接口 + listSonLandTransferLedger, + addSonLandTransferLedger } from '@/api/system/landTransfer/landTransferLedger'; import { listEnterRoad } from '@/api/system/landTransfer/enterRoad'; import { LandTransferLedgerVO, LandTransferLedgerQuery, LandTransferLedgerForm } from '@/api/system/landTransfer/landTransferLedger/types'; @@ -231,6 +544,7 @@ import { useUserStoreHook } from '@/store/modules/user'; import { listLandBlock } from '@/api/system/landTransfer/landBlock'; import { getCurrentInstance, ComponentInternalInstance, watch, onUnmounted, onMounted } from 'vue'; import { ElFormInstance } from 'element-plus'; +import { area } from '@turf/turf'; // 类型定义补充 interface DialogOption { @@ -263,14 +577,47 @@ const enterRoadList = ref([]); // 字典数据 const { land_type, land_transfer_status } = toRefs<any>(proxy?.useDict('land_type', 'land_transfer_status')); +// 主项弹窗配置 const dialog = reactive<DialogOption>({ visible: false, title: '' }); +// 子项相关状态 +const sonDialog = reactive<DialogOption>({ + visible: false, + title: '子项列表' +}); + +const sonFormDialog = reactive<DialogOption>({ + visible: false, + title: '新增子项' +}); + +const sonLandTransferLedgerList = ref<LandTransferLedgerVO[]>([]); +const sonLoading = ref(false); +const sonTotal = ref(0); +const sonIds = ref<Array<string | number>>([]); +const sonButtonLoading = ref(false); +const sonLandTransferLedgerFormRef = ref<ElFormInstance>(); + +// 当前父项ID,用于子项操作 +const currentParentId = ref<number | string | undefined>(undefined); + +// 子项查询参数 +const sonQueryParams = reactive<LandTransferLedgerQuery>({ + pageNum: 1, + pageSize: 10, + projectId: currentProject.value?.id, + parentId: undefined, + params: {}, + listType: 2 // 标识为子项列表 +}); + // 表单初始数据 const initFormData: LandTransferLedgerForm = { id: undefined, + parentId: undefined, projectId: currentProject.value?.id, landType: undefined, landBlockId: undefined, @@ -316,10 +663,9 @@ const data = reactive<PageData<LandTransferLedgerForm, LandTransferLedgerQuery>> issueSummary: undefined, nextStrategy: undefined, params: {}, - listType: 1 + listType: 1 // 标识为主项列表 }, rules: { - id: [{ required: true, message: '主键ID不能为空', trigger: 'blur' }], projectId: [{ required: true, message: '项目ID不能为空', trigger: 'blur' }], landType: [{ required: true, message: '土地类型不能为空', trigger: 'change' }], transferRatio: [ @@ -355,10 +701,86 @@ const data = reactive<PageData<LandTransferLedgerForm, LandTransferLedgerQuery>> } }); +// 子项表单数据 +const sonForm = reactive<LandTransferLedgerForm>({ ...initFormData }); + +// 子项表单验证规则(与主项相同) + +// 根据transferStatus分类的子项列表 +const sonTransferLedgerList0 = computed(() => sonLandTransferLedgerList.value.filter((item) => item.transferStatus === '0')); + +const sonTransferLedgerList1 = computed(() => sonLandTransferLedgerList.value.filter((item) => item.transferStatus === '1')); + +const sonTransferLedgerList2 = computed(() => sonLandTransferLedgerList.value.filter((item) => item.transferStatus === '2')); + +// 当前父项完整数据 +const currentParentItem = ref<LandTransferLedgerVO | null>(null); + +// 子项列表统计信息 +const sonSummaryInfo = computed(() => { + // 1. 计算子项已流转面积总和(使用areaValue字段,当transferStatus为1时) + const totalTransferArea = sonTransferLedgerList1.value.reduce((sum, item) => { + return sum + (parseFloat(item.areaValue || '0') || 0); + }, 0); + + // 2. 计算子项不流转面积总和(使用areaValue字段,当transferStatus为2时) + const totalNonTransferArea = sonTransferLedgerList2.value.reduce((sum, item) => { + return sum + (parseFloat(item.areaValue || '0') || 0); + }, 0); + + // 3. 获取父项设计面积 + const parentDesignArea = parseFloat(currentParentItem.value?.designArea || '0') || 0; + + // 4. 计算未流转面积 + const remainingArea = Math.max(0, parentDesignArea - totalTransferArea - totalNonTransferArea); + + return { + parentDesignArea, + totalTransferArea, + totalNonTransferArea, + remainingArea + }; +}); + +// 最近选择的父项数据,用于新增弹窗预填充 +const lastSelectedParent = ref<LandTransferLedgerVO | null>(null); +const sonRules = { + projectId: [{ required: true, message: '项目ID不能为空', trigger: 'blur' }], + landType: [{ required: true, message: '土地类型不能为空', trigger: 'change' }], + transferRatio: [ + { + required: true, + message: '流转比例不能为空', + trigger: ['blur', 'change'], + validator: (rule, value, callback) => { + if (sonForm.transferStatus !== '1') { + callback(); + return; + } + if (value === undefined || value === null || value === '') { + callback(new Error('流转比例不能为空')); + } else { + callback(); + } + } + }, + { + validator: (rule, value, callback) => { + if (value < 0 || value > 100) { + callback(new Error('流转比例必须在 0-100 之间')); + } else { + callback(); + } + }, + trigger: 'blur' + } + ] +}; + const detailInfo = ref({ - transferAea: 0, - transferRatio: 0, - landRent: 0 + areaSum: 0, + rentSum: 0, + transferAreaSum: 0 }); const { queryParams, form, rules } = toRefs(data); @@ -374,7 +796,7 @@ const calcTransferRatio = () => { // 转换为数字(避免字符串计算) const designArea = Number(form.value.designArea) || 0; - const transferAea = Number(form.value.transferAea) || 0; + const areaValue = Number(form.value.areaValue) || 0; // 边界处理:设计面积为0时避免报错 if (designArea === 0) { @@ -383,44 +805,156 @@ const calcTransferRatio = () => { } // 核心计算:限制最大100%,保留2位小数 - const ratio = Math.min((transferAea / designArea) * 100, 100); + const ratio = Math.min((areaValue / designArea) * 100, 100); form.value.transferRatio = Number(ratio.toFixed(2)); }; -/** 查询项目土地流转台账列表 */ +/** + * 子项自动计算流转比例 + */ +const calcSonTransferRatio = () => { + // 仅已流转状态下计算 + if (sonForm.transferStatus !== '1') { + sonForm.transferRatio = undefined; + return; + } + + // 转换为数字(避免字符串计算) + const designArea = Number(sonForm.designArea) || 0; + const areaValue = Number(sonForm.areaValue) || 0; + + // 边界处理:设计面积为0时避免报错 + if (designArea === 0) { + sonForm.transferRatio = 0; + return; + } + + // 核心计算:限制最大100%,保留2位小数 + const ratio = Math.min((areaValue / designArea) * 100, 100); + sonForm.transferRatio = Number(ratio.toFixed(2)); +}; + +/** 查询项目土地流转台账列表(主项) */ const getList = async () => { loading.value = true; try { const res = await listLandTransferLedger(queryParams.value); - landTransferLedgerList.value = res.rows; - total.value = res.total; + console.log('主项接口返回数据:', res); + console.log('主项数据赋值前:', landTransferLedgerList.value); + landTransferLedgerList.value = res.rows || []; + total.value = res.total || 0; + console.log('主项数据赋值后:', landTransferLedgerList.value); } finally { loading.value = false; } }; +/** 查询子项列表 */ +const getSonList = async () => { + sonLoading.value = true; + try { + const res = await listSonLandTransferLedger(sonQueryParams); + console.log('子项接口返回数据:', res); // 查看res是否有rows,且rows非空 + console.log('子项数据赋值前:', sonLandTransferLedgerList.value); -/** 获取地块统计信息 */ + // 处理接口返回的数据,添加缺失的显示属性 + const processedData = (res.data || []).map((item) => ({ + ...item, + // 添加显示所需的属性 + landTypeName: getLandTypeName(item.landType), + landName: getLandBlockName(item.landBlockId), + transferStatusName: getTransferStatusName(item.transferStatus) + })); + + sonLandTransferLedgerList.value = processedData; + sonTotal.value = res.total || 0; + console.log('子项数据赋值后:', sonLandTransferLedgerList.value); // 确认数组有数据 + } finally { + sonLoading.value = false; + } +}; + +/** 根据土地类型值获取名称 */ +const getLandTypeName = (value) => { + if (!value) return ''; + const landTypeItem = land_type?.value?.find((item) => item.value === value); + return landTypeItem?.label || ''; +}; + +/** 根据地块ID获取地块名称 */ +const getLandBlockName = (id) => { + if (!id) return ''; + const landBlockItem = landBlockList.value?.find((item) => item.id === id); + return landBlockItem?.landName || ''; +}; + +/** 根据流转状态值获取名称 */ +const getTransferStatusName = (value) => { + if (!value) return ''; + const statusItem = land_transfer_status?.value?.find((item) => item.value === value); + return statusItem?.label || ''; +}; + +/** 获取地块统计信息 - 调用listallCountValue接口 */ const getLandBlockList = async () => { try { - const res = await landTransferLedgerCount(currentProject.value?.id); - detailInfo.value = res.data; + // 获取当前选中的项目ID + const projectId = currentProject.value?.id; + if (!projectId) { + console.error('项目ID不存在,无法获取统计信息'); + return; + } + + // 调用listallCountValue接口,传入projectId参数 + const res = await listallCountValue(projectId); + + // 映射返回的数据到detailInfo对象,确保与模板中使用的字段一致 + detailInfo.value = { + areaSum: res.data.areaSum, + rentSum: res.data.rentSum, + transferAreaSum: res.data.transferAreaSum + }; } catch (error) { console.error('获取地块统计信息失败:', error); } }; -/** 取消按钮 */ +/** 取消按钮(主项) */ const cancel = () => { dialog.visible = false; - reset(); + resetForm(); }; -/** 表单重置 */ -const reset = () => { - form.value = { ...initFormData }; +/** 取消按钮(子项) */ +const cancelSonForm = () => { + sonFormDialog.visible = false; + getLandBlockList(); // 关闭子项时刷新顶部实时信息 + resetSonForm(); + getList(); // 关闭子项弹窗时刷新父级列表 +}; + +/** 关闭子项列表弹窗 */ +const handleCloseSonDialog = () => { + sonDialog.visible = false; + getList(); // 关闭子项列表时刷新父级列表 + getLandBlockList(); // 关闭子项时刷新顶部实时信息 +}; + +/** 表单重置(主项) */ +const resetForm = () => { + form.value = { ...initFormData, projectId: currentProject.value?.id }; landTransferLedgerFormRef.value?.resetFields(); }; +/** 表单重置(子项) */ +const resetSonForm = () => { + // 不要重新赋值整个对象,而是修改其属性 + Object.assign(sonForm, { + ...initFormData, + projectId: currentProject.value?.id, + parentId: currentParentId.value + }); + sonLandTransferLedgerFormRef.value?.resetFields(); +}; /** 搜索按钮操作 */ const handleQuery = () => { queryParams.value.pageNum = 1; @@ -433,58 +967,114 @@ const resetQuery = () => { handleQuery(); }; -/** 多选框选中数据 */ +/** 多选框选中数据(主项) */ const handleSelectionChange = (selection: LandTransferLedgerVO[]) => { ids.value = selection.map((item) => item.id); single.value = selection.length !== 1; multiple.value = selection.length === 0; }; -/** 新增按钮操作 */ +/** 多选框选中数据(子项) */ +const handleSonSelectionChange = (selection: LandTransferLedgerVO[]) => { + sonIds.value = selection.map((item) => item.id); +}; + +/** 新增主项按钮操作 */ const handleAdd = () => { - reset(); + resetForm(); form.value.transferStatus = '0'; // 默认待流转 enterRoadList.value = []; dialog.title = '添加项目土地流转台账'; dialog.visible = true; }; -/** 修改按钮操作 */ -const handleUpdate = async (row?: LandTransferLedgerVO) => { - reset(); - const _id = row?.id || ids.value[0]; - if (!_id) return; +/** 查看子项按钮操作(打开子项弹窗时) */ +const handleViewSons = async (row: LandTransferLedgerVO) => { + if (!row?.id) return; - try { - // 获取编辑数据 - const res = await getLandTransferLedger(_id); - Object.assign(form.value, res.data); - // 回显地块对应的道路列表 - form.value.landBlockId = row?.landBlockId; - await getListRoad(); - // 初始化计算流转比例 - calcTransferRatio(); - // 打开弹窗 - dialog.title = '修改项目土地流转台账'; - dialog.visible = true; - } catch (error) { - console.error('获取编辑数据失败:', error); - proxy?.$modal.msgError('加载数据失败,请重试'); - } + // 1. 确认父项ID已赋值 + currentParentId.value = row.id; + console.log('当前父项ID:', currentParentId.value); // 确认非空 + + // 2. 保存当前父项的完整数据,用于计算统计信息 + currentParentItem.value = row; + + // 2. 确认项目ID已赋值(从用户store获取) + const projectId = currentProject.value?.id; + console.log('当前项目ID:', projectId); // 确认非空 + + // 3. 设置子项查询参数(必须包含parentId和projectId) + sonQueryParams.parentId = currentParentId.value; + sonQueryParams.projectId = projectId; + sonQueryParams.pageNum = 1; // 重置为第1页,避免分页导致数据不显示 + + // 4. 重新加载子项列表 + await getSonList(); + sonDialog.visible = true; }; -/** 提交按钮 */ +/** 新增子项按钮操作 - 从父项继承数据 */ +const handleAddSon = async () => { + if (!currentParentId.value) { + proxy?.$modal.msgWarning('请先选择父项记录'); + return; + } + + // 1. 查找当前父项的完整数据 + const parentItem = landTransferLedgerList.value.find((item) => item.id === currentParentId.value); + + if (!parentItem) { + proxy?.$modal.msgWarning('未找到父项数据'); + return; + } + + // 2. 重置表单并继承父项数据 + resetSonForm(); + + // 3. 核心:从父项继承基础数据(根据实际业务调整需要继承的字段) + Object.assign(sonForm, { + // 必传的关联字段 + parentId: currentParentId.value, + projectId: currentProject.value?.id, + + // 继承父项的核心字段(示例,需根据实际接口字段调整) + landBlockId: parentItem.landBlockId, // 继承“对应地块” + landType: parentItem.landType, // 继承“土地类型” + enterRoadId: parentItem.enterRoadId, // 继承“进场道路” + designArea: parentItem.designArea, // 继承“设计面积” + responsiblePerson: parentItem.responsiblePerson, // 继承“责任人” + expectedFinishDate: parentItem.expectedFinishDate, // 继承“预计完成时间” + + // 子项默认值(可覆盖父项或设置新值) + transferStatus: '1', // 默认为“已流转” + areaValue: 0, + transferRatio: 0 + }); + + // 4. 继承后同步加载关联数据(如进场道路列表) + await getListRoad(); + + // 5. 打开弹窗 + sonFormDialog.title = '添加子项(继承父项数据)'; + sonFormDialog.visible = true; +}; + +/** 按流转状态新增子项按钮操作 */ +const handleAddSonByStatus = (status: string) => { + handleAddSon(); + // 设置指定的流转状态 + sonForm.transferStatus = status; +}; + +/** 提交主项表单 */ const submitForm = () => { landTransferLedgerFormRef.value?.validate(async (valid: boolean) => { if (!valid) return; buttonLoading.value = true; try { - if (form.value.id) { - await updateLandTransferLedger(form.value); - } else { - await addLandTransferLedger(form.value); - } + // 只保留新增功能,移除修改功能 + await addLandTransferLedger(form.value); proxy?.$modal.msgSuccess('操作成功'); dialog.visible = false; await getList(); @@ -498,7 +1088,65 @@ const submitForm = () => { }); }; -/** 删除按钮操作 */ +/** 提交子项表单 */ +const submitSonForm = () => { + sonLandTransferLedgerFormRef.value?.validate(async (valid: boolean) => { + if (!valid) return; + + // 面积校验:已流转面积和不流转面积总和不能超过设计面积 + try { + // 1. 获取当前父项的设计面积 + const parentDesignArea = parseFloat(currentParentItem.value?.designArea || '0'); + if (parentDesignArea <= 0) { + proxy?.$modal.msgWarning('父项设计面积未设置或无效'); + return; + } + + // 2. 获取当前子项的面积 + const currentArea = parseFloat(sonForm.areaValue || '0'); + if (currentArea <= 0) { + proxy?.$modal.msgWarning('请输入有效的面积值'); + return; + } + + // 3. 计算当前所有子项的面积总和(排除当前正在编辑的记录) + let totalExistingArea = 0; + if (sonLandTransferLedgerList.value && sonLandTransferLedgerList.value.length > 0) { + totalExistingArea = sonLandTransferLedgerList.value.reduce((sum, item) => { + return sum + (parseFloat(item.areaValue || '0') || 0); + }, 0); + } + + // 4. 计算总面积(现有面积 + 当前子项面积) + const totalArea = totalExistingArea + currentArea; + + // 5. 校验总面积是否超过设计面积 + if (totalArea > parentDesignArea) { + proxy?.$modal.msgError(`面积总和 ${totalArea} 亩超过了设计面积 ${parentDesignArea} 亩,请调整面积值`); + return; + } + } catch (error) { + console.error('面积校验失败:', error); + proxy?.$modal.msgError('面积校验失败,请重试'); + return; + } + + sonButtonLoading.value = true; + try { + await addSonLandTransferLedger(sonForm); + proxy?.$modal.msgSuccess('子项添加成功'); + sonFormDialog.visible = false; + await getSonList(); // 刷新子项列表 + } catch (error) { + proxy?.$modal.msgError('操作失败,请重试'); + console.error('提交子项表单失败:', error); + } finally { + sonButtonLoading.value = false; + } + }); +}; + +/** 删除主项按钮操作 */ const handleDelete = async (row?: LandTransferLedgerVO) => { const _ids = row?.id || ids.value; if (!_ids.length) return; @@ -542,11 +1190,14 @@ const getListLand = async () => { /** 查询进场道路列表(按地块筛选) */ const getListRoad = async () => { try { + // 优先使用子项表单的地块ID,否则使用主项表单的 + const landBlockId = sonForm.landBlockId || form.value.landBlockId; + const res = await listEnterRoad({ pageNum: 1, pageSize: 10000, projectId: currentProject.value?.id, - landBlockId: form.value.landBlockId + landBlockId }); enterRoadList.value = res.rows; } catch (error) { @@ -554,16 +1205,17 @@ const getListRoad = async () => { } }; -/** 监听项目切换,刷新数据 */ +// 监听项目切换时,重置主项查询参数 const listeningProject = watch( () => currentProject.value?.id, async (newId) => { if (newId) { - queryParams.value.projectId = newId; + queryParams.value.projectId = newId; // 确保项目ID赋值 + queryParams.value.pageNum = 1; // 重置页码 await Promise.all([getLandBlockList(), getListLand(), getList()]); } }, - { immediate: true } // 初始加载时触发 + { immediate: true } ); /** 组件卸载时清理监听 */ @@ -608,4 +1260,107 @@ onMounted(() => { } } } + +// 子项列表统计展示区域美化样式 +.summary-box { + width: 100%; + display: flex; + justify-content: space-between; + margin: 20px 0; + padding: 15px; + background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); + border-radius: 12px; + box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1); + + // 响应式布局 + @media (max-width: 768px) { + flex-wrap: wrap; + > .summary-item { + width: calc(50% - 10px) !important; + margin-bottom: 15px; + } + } +} + +.summary-item { + flex: 1; + display: flex; + justify-content: space-between; + align-items: center; + padding: 20px 15px; + background: white; + margin: 0 8px; + border-radius: 10px; + border: 1px solid #e1e5e9; + transition: all 0.3s ease; + + &:first-child { + margin-left: 0; + } + + &:last-child { + margin-right: 0; + } + + &:hover { + transform: translateY(-2px); + box-shadow: 0 6px 20px rgba(49, 118, 255, 0.15); + border-color: #3176ff; + } +} + +.summary-content { + display: flex; + flex-direction: column; +} + +.summary-label { + font-size: 14px; + color: #666; + margin-bottom: 8px; + font-weight: 500; +} + +.summary-value { + font-size: 24px; + color: #333; + font-weight: bold; + transition: color 0.3s ease; +} + +// 不同类型数据的特殊样式 +.design-area .summary-value { + color: #3176ff; +} + +.transfer-area .summary-value { + color: #52c41a; +} + +.non-transfer-area .summary-value { + color: #ff6b6b; +} + +.remaining-area .summary-value { + color: #fa8c16; +} + +.summary-icon { + transition: transform 0.3s ease; +} + +.summary-item:hover .summary-icon { + transform: scale(1.1); +} + +// 调整弹窗表单样式,适应竖向排列 +.el-dialog__body { + max-height: 70vh; + overflow-y: auto; + padding: 20px; +} + +.el-form-item { + margin-bottom: 15px; +} </style> diff --git a/src/views/project/project/index.vue b/src/views/project/project/index.vue index 04ec6b7..dc8ec1b 100644 --- a/src/views/project/project/index.vue +++ b/src/views/project/project/index.vue @@ -144,13 +144,16 @@ </el-table-column> --> <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']" @@ -160,7 +163,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> <!-- 添加或修改项目对话框 --> @@ -253,78 +255,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"> @@ -342,13 +272,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" @@ -356,7 +285,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> @@ -366,7 +295,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"> @@ -375,6 +304,91 @@ </div> </template> </el-dialog> + <el-dialog draggable title="打卡规则" v-model="ruleFlag" width="800"> + <template #footer> + <el-form ref="projectFormRef" :model="form" :rules="rules" label-width="100px"> + <el-row :gutter="20"> + <el-col :span="12" :offset="0"> + <el-form-item label="上班时间" prop="clockInTime"> + <el-time-select + v-model="form.clockInTime" + 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="clockOutTime"> + <el-time-select + v-model="form.clockOutTime" + style="width: 100%" + :min-time="form.clockInTime" + 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="type"> + <el-radio-group v-model="form.type"> + <el-radio value="1" size="large">无限制</el-radio> + <el-radio value="2" size="large">范围内打卡</el-radio> + </el-radio-group> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="打卡类型" prop="weekday"> + <el-checkbox-group v-model="form.weekday" size="small"> + <el-checkbox label="星期一" value="1" /> + <el-checkbox label="星期二" value="2" /> + <el-checkbox label="星期三" value="3" /> + <el-checkbox label="星期四" value="4" /> + <el-checkbox label="星期五" value="5" /> + <el-checkbox label="星期六" value="6" /> + <el-checkbox label="星期日" value="7" /> + </el-checkbox-group> + </el-form-item> + </el-col> </el-row + ></el-form> + <div class="dialog-footer"> + <el-button type="primary" @click="ruleSubmit"> 提交</el-button> + <el-button @click="ruleFlag = false">取消</el-button> + </div> + </template> + </el-dialog> + <el-dialog draggable title="打卡范围" v-model="ScopeFlag" width="600"> + <div v-for="(item, i) of punchRangeList" :key="i" class="options_item"> + <el-row style="margin-bottom: 10px;"> + <el-col :span="1"> <el-color-picker v-model="item.punchColor" show-alpha /></el-col> + <el-col :span="12"> <el-input v-model="item.punchName" placeholder="请输入打卡范围名称" class="ml-8" /></el-col> + <el-col :span="10" style="text-align: right; margin-top: 5px"> + <el-button v-if="item.id" link type="primary" icon="view" @click="previewPunchRange(item)">预览</el-button> + <el-button v-if="item.id" link type="primary" icon="delete" @click="handleScopeDel(item)">移除</el-button> + <el-button v-if="!item.id" link type="primary" icon="plus" @click="addPunchRange(item)">添加</el-button> + <el-button v-if="item.id" link type="primary" icon="download" @click="handleScopeEdit(item)">保存</el-button> + </el-col> + </el-row> + </div> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="scopeSubmit">确定</el-button> + <el-button @click="ScopeFlag = false">取消</el-button> + </div> + </template> + </el-dialog> + <CesiumEarthDialog ref="earthDialog" @send-data="handleEarthData" + :position="position" + :data="earthData" + @close="handleEarthClose"></CesiumEarthDialog> </div> </template> @@ -382,17 +396,24 @@ 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'; +import CesiumEarthDialog from "./map.vue" +import { useUserStoreHook } from '@/store/modules/user'; +const userStore = useUserStoreHook(); +const currentProject = computed(() => userStore.selectedProject); const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { sys_normal_disable, project_category_type, project_type, project_stage } = toRefs<any>( proxy?.useDict('sys_normal_disable', 'project_category_type', 'project_type', 'project_stage') @@ -413,6 +434,22 @@ const polygonStatus = ref(false); const dxfFile = ref(null); const projectId = ref<string>(''); const designId = ref<string>(''); +const ruleFlag = ref(false); +const ScopeFlag = ref(false); +const earthDialog = ref(null); +const position = ref(''); //预览 +let earthData = reactive({ + projectId: '', + punchName: '', + punchColor: '#1983ff', + punchRange: '', +}); //传值给地图组件 +const punchRangeList = ref<any>([ + { + punchName: '', + punchColor: '#1983ff' + } +]); const childProjectForm = reactive<childProjectQuery>({ projectName: '', pid: '', @@ -432,7 +469,7 @@ const fileForm = ref({ const jsonData = ref(null); const fullscreenLoading = ref(false); -const initFormData: ProjectForm = { +const initFormData = { id: undefined, projectName: undefined, shortName: undefined, @@ -451,13 +488,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 }, @@ -480,8 +519,6 @@ const data = reactive<PageData<ProjectForm, ProjectQuery>>({ lat: undefined, plan: undefined, onStreamTime: undefined, - playCardStart: undefined, - playCardEnd: undefined, designTotal: undefined, securityAgreement: undefined, sort: undefined, @@ -490,8 +527,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' }], @@ -692,7 +729,86 @@ const handleSetChild = async () => { } } }; +const handleScope = (row) => { + console.log('row',row); + // 打卡范围 + ScopeFlag.value = true; + projectId.value = row.id; + // 获取打卡范围列表 + getListScope(row.id); +}; +// 获取打卡范围列表 +const getListScope = (id) => { + punchRangeList.value = [{ + punchName: '', + punchColor: '#1983ff' + }]; + getAttendanceRangeList({ projectId: id, }).then((res) => { + if (res.code === 200) { + punchRangeList.value.unshift(...res.rows); + } + }); +} +// 修改打卡范围 +const handleScopeEdit = (row) => { + updateAttendanceRange(row).then((res) => { + if (res.code === 200) { + proxy.$modal.msgSuccess('修改成功'); + // ScopeFlag.value = false; + getListScope(projectId.value); + } + }); +} +// 删除打卡范围 +const handleScopeDel = (row) => { + proxy.$modal.confirm('是否确认删除打卡范围?').then(() => { + delAttendanceRange(row.id).then((res) => { + if (res.code === 200) { + proxy.$modal.msgSuccess('删除成功'); + getListScope(projectId.value); + } + }); + }).catch(() => {}); +} + +const scopeSubmit = () => { + // 提交打卡范围 +}; +// 添加规则 +const handleCheckRules = async (row?: ProjectVO) => { + reset(); + const _id = row?.id || ids.value[0]; + const res = await byProjectIdDetail(_id); + if (res.data) { + res.data.weekday = res.data.weekday.split(','); + Object.assign(form.value, res.data); + } + projectId.value = row.id; + ruleFlag.value = true; +}; +const ruleSubmit = async () => { + console.log(form.value); + projectFormRef.value?.validate(async (valid: boolean) => { + if (valid) { + let obj = { + weekday: form.value.weekday.join(','), + projectId: projectId.value, + id: projectId.value, + clockInTime: form.value.clockInTime, + clockOutTime: form.value.clockOutTime, + type: form.value.type + }; + if (form.value.id) { + await attendanceRuleEdit(obj); + } else { + await attendanceRuleAdd(obj); + } + ruleFlag.value = false; + await getList(); + } + }); +}; /** 导出按钮操作 */ const handleExport = () => { proxy?.download( @@ -703,7 +819,36 @@ const handleExport = () => { `project_${new Date().getTime()}.xlsx` ); }; - +// 打开绘制范围 +const addPunchRange = (item) => { + earthData.punchName = item.punchName + earthData.punchColor = item.punchColor + earthData.projectId = projectId.value + earthData.id = '' + earthData.punchRange = '' + if (earthData.punchName=='') { + proxy.$modal.msgError('请先填写打卡范围名称'); + return + } + earthDialog.value.show() +} +// 接受绘制范围 +const handleEarthData = (data) => { + ScopeFlag.value = false; +} +// 关闭绘制范围 +const handleEarthClose = () => { + earthDialog.value.show = false +} +// 预览范围 +const previewPunchRange = (item)=>{ + earthData.id = item.id + earthData.projectId = item.projectId + earthData.punchName = item.punchName + earthData.punchColor = item.punchColor + earthData.punchRange = item.punchRange + earthDialog.value.show() +} onMounted(() => { getList(); }); diff --git a/src/views/project/project/map.vue b/src/views/project/project/map.vue new file mode 100644 index 0000000..3159d97 --- /dev/null +++ b/src/views/project/project/map.vue @@ -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> diff --git a/src/views/project/projectTeam/index.vue b/src/views/project/projectTeam/index.vue index 81d08f3..0e70d0d 100644 --- a/src/views/project/projectTeam/index.vue +++ b/src/views/project/projectTeam/index.vue @@ -80,6 +80,11 @@ <el-option v-for="item in team_clock_type" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </el-form-item> + <el-form-item label="打卡范围" prop="punchRangeList" v-if="form.isClockIn == 1"> + <el-select v-model="form.punchRangeList" multiple clearable placeholder="请选择打卡范围"> + <el-option v-for="item in projectTeamRangeList" :key="item.id" :label="item.punchName" :value="item.id" /> + </el-select> + </el-form-item> <el-form-item label="备注" prop="remark"> <el-input v-model="form.remark" type="textarea" placeholder="请输入内容" /> </el-form-item> @@ -98,13 +103,15 @@ </template> <script setup name="ProjectTeam" lang="ts"> -import { addProjectTeam, delProjectTeam, getProjectTeam, listProjectTeam, updateProjectTeam } from '@/api/project/projectTeam'; +import { addProjectTeam, delProjectTeam, getProjectTeam, listProjectTeam, updateProjectTeam,getProjectTeamClockIn } from '@/api/project/projectTeam'; import { ProjectTeamForm, ProjectTeamQuery, ProjectTeamVO } from '@/api/project/projectTeam/types'; import { useUserStoreHook } from '@/store/modules/user'; import UserListDialog from '@/views/project/projectTeam/component/UserListDialog.vue'; 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 中获取项目列表和当前选中的项目 @@ -117,6 +124,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, @@ -140,7 +148,8 @@ const initFormData: ProjectTeamForm = { teamName: undefined, isClockIn: undefined, remark: undefined, - peopleNumber: undefined + peopleNumber: undefined, + punchRangeList: undefined }; const data = reactive<PageData<ProjectTeamForm, ProjectTeamQuery>>({ form: { ...initFormData }, @@ -171,6 +180,14 @@ const getList = async () => { total.value = res.total; loading.value = false; }; +/** 获取该项目的打开范围 "*/ +const getClockIn = async () => { + if(currentProject.value?.id){ + const res = await getProjectTeamClockIn({projectId:currentProject.value?.id}); + projectTeamRangeList.value = res.rows + } +}; + /** 取消按钮 */ const cancel = () => { @@ -280,5 +297,6 @@ onUnmounted(() => { onMounted(() => { getList(); + getClockIn(); }); </script> diff --git a/src/views/project/projectUser/index.vue b/src/views/project/projectUser/index.vue index 1429ed6..7df404d 100644 --- a/src/views/project/projectUser/index.vue +++ b/src/views/project/projectUser/index.vue @@ -5,6 +5,11 @@ <div v-show="showSearch" class="mb-[10px]"> <el-card shadow="hover"> <el-form ref="queryFormRef" :model="queryParams" :inline="true"> + <el-form-item label="项目名称" prop="projectId"> + <el-select v-model="queryParams.projectId" clearable placeholder="全部"> + <el-option v-for="item in projectList" :key="item.value" :label="item.projectName" :value="item.id" /> + </el-select> + </el-form-item> <el-form-item label="人员姓名" prop="userName"> <el-input v-model="queryParams.userName" placeholder="请输入人员姓名" clearable @keyup.enter="handleQuery" /> </el-form-item> @@ -156,6 +161,7 @@ </el-button> <!-- <el-button link type="primary" icon="Switch" @click="handleToggle(scope.row)"> 切换人脸 </el-button> --> <el-button link type="primary" icon="Switch" @click="handleChange(scope.row)"> 人员迁移 </el-button> + <el-button link type="primary" icon="Switch" @click="handleAssign(scope.row)"> 分配班组 </el-button> <el-button link type="primary" icon="ChatLineSquare" @click="handleExit(scope.row)"> 入退场记录 </el-button> <el-button link type="danger" icon="Delete" @click="handleDelete(scope.row)" v-hasPermi="['contractor:constructionUser:remove']"> @@ -443,6 +449,30 @@ </template> </el-calendar> </el-dialog> + <el-dialog draggable :title="skipName + '-人员分配'" v-model="personnelAllocation" width="500px"> + <el-form-item label="所属项目" label-width="130px"> + <el-select v-model="personnelAllocationObject.projectId" @change="selectProject1" placeholder="请选择所属项目" style="width: 240px"> + <el-option v-for="item in projectList" :key="item.id" :label="item.projectName" :value="item.id" /> + </el-select> + </el-form-item> + <el-form-item label="岗位" label-width="130px"> + <el-select v-model="personnelAllocationObject.postId" placeholder="请选择岗位" style="width: 240px"> + <el-option v-for="item in user_post_type" :key="item.value" :label="item.label" :value="item.value" /> + </el-select> + </el-form-item> + <el-form-item label="班组" label-width="130px"> + <el-select v-model="personnelAllocationObject.teamId" :disabled="!personnelAllocationObject.projectId" placeholder="请选择分包单位" + style="width: 240px"> + <el-option v-for="item in teamList" :key="item.id" :label="item.teamName" :value="item.id" /> + </el-select> + </el-form-item> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="handlePersonnelAllocation">确认</el-button> + <el-button @click="personnelAllocation = false"> 取消 </el-button> + </div> + </template> + </el-dialog> </div> </template> @@ -462,7 +492,10 @@ import { getConstructionUserExit, dowloadConstructionUserTemplate, importConstructionUserInfo, - listConstructionMonth + listConstructionMonth, + ProjectList, + TeamList, + TeamDistribution } from '@/api/project/constructionUser'; import { ConstructionUserForm, @@ -494,8 +527,8 @@ import { parseTime } from '@/utils/ruoyi'; const calendar = ref<CalendarInstance>(); const { proxy } = getCurrentInstance() as ComponentInternalInstance; -const { type_of_work, user_sex_type, user_clock_type, user_file_type, user_status_type, wage_measure_unit_type } = toRefs<any>( - proxy?.useDict('type_of_work', 'user_sex_type', 'user_clock_type', 'user_file_type', 'user_status_type', 'wage_measure_unit_type') +const { type_of_work, user_sex_type, user_clock_type, user_file_type, user_status_type, wage_measure_unit_type,user_post_type } = toRefs<any>( + proxy?.useDict('type_of_work', 'user_sex_type', 'user_clock_type', 'user_file_type', 'user_status_type', 'wage_measure_unit_type','user_post_type') ); // 获取用户 store const userStore = useUserStoreHook(); @@ -511,6 +544,7 @@ const single = ref(true); const multiple = ref(true); const total = ref(0); const skip = ref(false); +const personnelAllocation = ref(false); const fileStatus = ref(false); const showFaceDrawer = ref(false); const statusDialog = ref(false); @@ -531,6 +565,10 @@ const queryFormRef = ref<ElFormInstance>(); const constructionUserFormRef = ref<ElFormInstance>(); const skipName = ref(''); const calendarList = ref<Array<AttendanceMonthVO>>([]); +// 项目列表 +const projectList = ref([]); +// 班组列表 +const teamList = ref([]); const dialog = reactive<DialogOption>({ visible: false, title: '', @@ -543,6 +581,14 @@ const skipObject: skipType = reactive({ projectId: '', contractorId: '' }); +// 人员分配 +const personnelAllocationObject = reactive({ + memberId: null, + projectId: '', + teamId: '', + postId: '', +}); + const contractorList = ref<Array<skipTeamType>>([]); //项目列表 const skipOptions = ref<Array<skipOptionType>>([]); @@ -661,6 +707,13 @@ const uploadPath = computed(() => { return list; }); +// 获取项目列表 +const getProjectList = async () => { + const res = await ProjectList({}); + projectList.value = res.rows; + projectList.value.unshift({ id: '', projectName: '全部' }); +}; + /** 返回文件上传状态 */ const uploadStatusColor = computed(() => (str: string) => { switch (str) { @@ -825,7 +878,7 @@ const reset = () => { /** 搜索按钮操作 */ const handleQuery = () => { queryParams.value.pageNum = 1; - if (contractorOpt.value.length == 1) queryParams.value.contractorId = contractorOpt.value[0].value; + // if (contractorOpt.value.length == 1) queryParams.value.contractorId = contractorOpt.value[0].value; getList(); }; @@ -1053,6 +1106,36 @@ const listeningProject = watch( getContractorList(); } ); +// 分配班组 +const handleAssign = async (row: ConstructionUserVO) => { + const _id = row?.id || ids.value[0]; + currentUserId.value = _id; + personnelAllocationObject.memberId = row?.sysUserId; + personnelAllocation.value = true; +}; +// 选择项目1 +const selectProject1 = (e: any) => { + // 请求班组 + getTeamList(personnelAllocationObject.projectId); + +}; +const getTeamList = async (projectId) => { + const res = await TeamList({ + projectId, + pageNum: 1, + pageSize: 100 + }); + teamList.value = res.rows; +}; +// 人员分配 +const handlePersonnelAllocation = async () => { + let res = await TeamDistribution(personnelAllocationObject); + if (res.code == 200) { + ElMessage.success(res.msg); + personnelAllocation.value = false; + getList(); + } +}; onUnmounted(() => { listeningProject(); @@ -1060,6 +1143,7 @@ onUnmounted(() => { onMounted(() => { getContractorList(); + getProjectList(); }); </script> <style scoped lang="scss"> diff --git a/src/views/system/appMenu/index.vue b/src/views/system/appMenu/index.vue new file mode 100644 index 0000000..47cecbe --- /dev/null +++ b/src/views/system/appMenu/index.vue @@ -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> diff --git a/src/views/system/appRole/authUser.vue b/src/views/system/appRole/authUser.vue new file mode 100644 index 0000000..0af7b6d --- /dev/null +++ b/src/views/system/appRole/authUser.vue @@ -0,0 +1,160 @@ +<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="search"> + <el-form ref="queryFormRef" :model="queryParams" :inline="true"> + <el-form-item label="用户名称" prop="userName"> + <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="手机号码" prop="phonenumber"> + <el-input v-model="queryParams.phonenumber" 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> + </div> + </transition> + <el-card shadow="never"> + <template #header> + <el-row :gutter="10"> + <el-col :span="1.5"> + <el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="openSelectUser">添加用户</el-button> + </el-col> + <el-col :span="1.5"> + <el-button v-hasPermi="['system:role:remove']" type="danger" plain icon="CircleClose" :disabled="multiple" @click="cancelAuthUserAll"> + 批量取消授权 + </el-button> + </el-col> + <el-col :span="1.5"> + <el-button type="warning" plain icon="Close" @click="handleClose">关闭</el-button> + </el-col> + <right-toolbar v-model:show-search="showSearch" :search="true" @query-table="getList"></right-toolbar> + </el-row> + </template> + <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" /> + <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" /> + <el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" /> + <el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" /> + <el-table-column label="状态" align="center" prop="status"> + <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" width="180"> + <template #default="scope"> + <span>{{ scope.row.createTime }}</span> + </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 v-hasPermi="['system:role:remove']" link type="primary" icon="CircleClose" @click="cancelAuthUser(scope.row)"> </el-button> + </el-tooltip> + </template> + </el-table-column> + </el-table> + + <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" /> + <select-user ref="selectRef" :role-id="queryParams.roleId" @ok="handleQuery" /> + </el-card> + </div> +</template> + +<script setup name="AuthUser" lang="ts"> +import { allocatedUserList, authUserCancel, authUserCancelAll } from '@/api/system/role'; +import { UserQuery } from '@/api/system/user/types'; +import { UserVO } from '@/api/system/user/types'; +import SelectUser from './selectUser.vue'; +import { RouteLocationNormalized } from 'vue-router'; + +const route = useRoute(); +const { proxy } = getCurrentInstance() as ComponentInternalInstance; +const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable')); + +const userList = ref<UserVO[]>([]); +const loading = ref(true); +const showSearch = ref(true); +const multiple = ref(true); +const total = ref(0); +const userIds = ref<Array<string | number>>([]); + +const queryFormRef = ref<ElFormInstance>(); +const selectRef = ref<InstanceType<typeof SelectUser>>(); + +const queryParams = reactive<UserQuery>({ + pageNum: 1, + pageSize: 10, + roleId: route.params.roleId as string, + userName: undefined, + phonenumber: undefined +}); + +/** 查询授权用户列表 */ +const getList = async () => { + loading.value = true; + const res = await allocatedUserList(queryParams); + userList.value = res.rows; + total.value = res.total; + loading.value = false; +}; +// 返回按钮 +const handleClose = () => { + const obj: RouteLocationNormalized = { + path: '/system/role', + fullPath: '', + hash: '', + matched: [], + meta: undefined, + name: undefined, + params: undefined, + query: undefined, + redirectedFrom: undefined + }; + proxy?.$tab.closeOpenPage(obj); +}; +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNum = 1; + getList(); +}; +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value?.resetFields(); + handleQuery(); +}; +// 多选框选中数据 +const handleSelectionChange = (selection: UserVO[]) => { + userIds.value = selection.map((item) => item.userId); + multiple.value = !selection.length; +}; +/** 打开授权用户表弹窗 */ +const openSelectUser = () => { + selectRef.value?.show(); +}; +/** 取消授权按钮操作 */ +const cancelAuthUser = async (row: UserVO) => { + await proxy?.$modal.confirm('确认要取消该用户"' + row.userName + '"角色吗?'); + await authUserCancel({ userId: row.userId, roleId: queryParams.roleId }); + await getList(); + proxy?.$modal.msgSuccess('取消授权成功'); +}; +/** 批量取消授权按钮操作 */ +const cancelAuthUserAll = async () => { + const roleId = queryParams.roleId; + const uIds = userIds.value.join(','); + await proxy?.$modal.confirm('是否取消选中用户授权数据项?'); + await authUserCancelAll({ roleId: roleId, userIds: uIds }); + await getList(); + proxy?.$modal.msgSuccess('取消授权成功'); +}; + +onMounted(() => { + getList(); +}); +</script> + +<style lang="scss" scoped></style> diff --git a/src/views/system/appRole/index.vue b/src/views/system/appRole/index.vue new file mode 100644 index 0000000..78bea58 --- /dev/null +++ b/src/views/system/appRole/index.vue @@ -0,0 +1,574 @@ +<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="roleName"> + <el-input v-model="queryParams.roleName" placeholder="请输入角色名称" clearable @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="权限字符" prop="roleKey"> + <el-input v-model="queryParams.roleKey" 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" v-hasPermi="['system:role:query']">搜索</el-button> + <el-button icon="Refresh" @click="resetQuery">重置</el-button> + </el-form-item> + </el-form> + </el-card> + </div> + </transition> + <el-row :gutter="20"> + <!-- 部门树 --> + <el-col :lg="4" :xs="24" style=""> + <el-card shadow="hover"> + <el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable /> + <el-tree + ref="deptTreeRef" + class="mt-2" + node-key="id" + :data="deptOptions" + :props="{ label: 'label', children: 'children' }" + :expand-on-click-node="false" + :filter-node-method="filterNode" + highlight-current + default-expand-all + @node-click="handleNodeClick" + /> + </el-card> + </el-col> + <el-col :lg="20" :xs="24"> + <el-card shadow="hover"> + <template #header> + <el-row :gutter="10"> + <el-col :span="1.5"> + <el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button> + </el-col> + <el-col :span="1.5"> + <el-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()" + >修改</el-button + > + </el-col> + <el-col :span="1.5"> + <el-button v-hasPermi="['system:role:delete']" type="danger" plain :disabled="ids.length === 0" @click="handleDelete()" + >删除</el-button + > + </el-col> + <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> + </el-row> + </template> + + <el-table ref="roleTableRef" v-loading="loading" :data="roleList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55" align="center" /> + <el-table-column v-if="false" label="角色编号" prop="roleId" width="120" /> + <el-table-column label="角色名称" prop="roleName" :show-overflow-tooltip="true" width="150" /> + <el-table-column label="权限字符" prop="roleKey" :show-overflow-tooltip="true" width="200" /> + <el-table-column label="显示顺序" prop="roleSort" width="100" /> + <el-table-column label="状态" align="center" width="100"> + <template #default="scope"> + <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch> + </template> + </el-table-column> + <el-table-column label="创建时间" align="center" prop="createTime"> + <template #default="scope"> + <span>{{ proxy.parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + + <el-table-column fixed="right" label="操作" width="180"> + <template #default="scope"> + <el-tooltip v-if="scope.row.roleId !== 1" content="修改" placement="top"> + <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button> + </el-tooltip> + <el-tooltip v-if="scope.row.roleId !== 1" content="删除" placement="top"> + <el-button v-hasPermi="['system:role:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button> + </el-tooltip> + <el-tooltip v-if="scope.row.roleId !== 1" content="数据权限" placement="top"> + <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)"></el-button> + </el-tooltip> + <el-tooltip v-if="scope.row.roleId !== 1" content="分配用户" placement="top"> + <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="User" @click="handleAuthUser(scope.row)"></el-button> + </el-tooltip> + </template> + </el-table-column> + </el-table> + + <pagination + v-if="total > 0" + v-model:total="total" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> + </el-card> + </el-col> + </el-row> + + <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body> + <el-form ref="roleFormRef" :model="form" :rules="rules" label-width="110px"> + <el-form-item label="所属部门" prop="deptId"> + <el-cascader + :options="deptOptions" + v-model="form.deptId" + placeholder="请选择所属部门" + clearable + filterable + :show-all-levels="false" + :props="{ value: 'id', emitPath: false, checkStrictly: true }" + > + </el-cascader> + </el-form-item> + <el-form-item label="角色名称" prop="roleName"> + <el-input v-model="form.roleName" placeholder="请输入角色名称" /> + </el-form-item> + <el-form-item prop="roleKey"> + <template #label> + <span> + <el-tooltip content="控制器中定义的权限字符,如:@SaCheckRole('admin')" placement="top"> + <el-icon><question-filled /></el-icon> + </el-tooltip> + 权限字符 + </span> + </template> + <el-input v-model="form.roleKey" placeholder="请输入权限字符" /> + </el-form-item> + <el-form-item label="角色顺序" prop="roleSort"> + <el-input-number v-model="form.roleSort" controls-position="right" :min="0" /> + </el-form-item> + <el-form-item label="状态"> + <el-radio-group v-model="form.status"> + <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio> + </el-radio-group> + </el-form-item> + <el-form-item label="菜单权限"> + <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand(Boolean($event), 'menu')">展开/折叠</el-checkbox> + <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox> + <el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox> + <el-tree + ref="menuRef" + class="tree-border" + :data="menuOptions" + show-checkbox + node-key="id" + :check-strictly="!form.menuCheckStrictly" + empty-text="加载中,请稍候" + :props="{ label: 'label', children: 'children' }" + ></el-tree> + </el-form-item> + <el-form-item label="是否为特殊角色"> + <el-switch v-model="form.isSpecial" active-value="1" inactive-value="0" active-text="是" inactive-text="否"> </el-switch> + </el-form-item> + <el-form-item label="备注"> + <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input> + </el-form-item> + </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> + + <!-- 分配角色数据权限对话框 --> + <el-dialog v-model="openDataScope" :title="dialog.title" width="500px" append-to-body> + <el-form ref="dataScopeRef" :model="form" label-width="80px"> + <el-form-item label="角色名称"> + <el-input v-model="form.roleName" :disabled="true" /> + </el-form-item> + <el-form-item label="权限字符"> + <el-input v-model="form.roleKey" :disabled="true" /> + </el-form-item> + <el-form-item label="权限范围"> + <el-select v-model="form.dataScope" @change="dataScopeSelectChange"> + <el-option v-for="item in dataScopeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option> + </el-select> + </el-form-item> + <el-form-item v-show="form.dataScope === '2'" label="数据权限"> + <el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand(Boolean($event), 'dept')">展开/折叠</el-checkbox> + <el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox> + <el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox> + <el-tree + ref="deptRef" + class="tree-border" + :data="deptOptions" + show-checkbox + default-expand-all + node-key="id" + :check-strictly="!form.deptCheckStrictly" + empty-text="加载中,请稍候" + :props="{ label: 'label', children: 'children' }" + ></el-tree> + </el-form-item> + </el-form> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitDataScope">确 定</el-button> + <el-button @click="cancelDataScope">取 消</el-button> + </div> + </template> + </el-dialog> + </div> +</template> + +<script setup name="Role" lang="ts"> +import { addRole, changeRoleStatus, dataScope, delRole, getRole, listRole, updateRole, deptTreeSelect } from '@/api/system/role'; +import { roleMenuTreeselect, treeselect as menuTreeselect } from '@/api/system/menu/index'; +import { RoleVO, RoleForm, RoleQuery, DeptTreeOption } from '@/api/system/role/types'; +import { MenuTreeOption, RoleMenuTree } from '@/api/system/menu/types'; +import api, { uploadCertList } from '@/api/system/user'; +import { DeptTreeVO, DeptVO } from '@/api/system/dept/types'; + +const router = useRouter(); +const { proxy } = getCurrentInstance() as ComponentInternalInstance; +const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable')); + +const roleList = ref<RoleVO[]>(); +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 dateRange = ref<[DateModelType, DateModelType]>(['', '']); +const menuOptions = ref<MenuTreeOption[]>([]); +const menuExpand = ref(false); +const menuNodeAll = ref(false); +const deptExpand = ref(true); +const deptNodeAll = ref(false); +const deptOptions = ref<DeptTreeVO[]>([]); +const enabledDeptOptions = ref<DeptTreeVO[]>([]); + +const openDataScope = ref(false); +const deptName = ref(''); + +/** 数据范围选项*/ +const dataScopeOptions = ref([ + { value: '1', label: '全部数据权限' }, + { value: '2', label: '自定数据权限' }, + { value: '3', label: '本部门数据权限' }, + { value: '4', label: '本部门及以下数据权限' }, + { value: '5', label: '仅本人数据权限' }, + { value: '6', label: '部门及以下或本人数据权限' } +]); + +const queryFormRef = ref<ElFormInstance>(); +const roleFormRef = ref<ElFormInstance>(); +const dataScopeRef = ref<ElFormInstance>(); +const menuRef = ref<ElTreeInstance>(); +const deptRef = ref<ElTreeInstance>(); +const deptTreeRef = ref<ElTreeInstance>(); + +const initForm = { + roleId: undefined, + roleSort: 1, + status: '0', + roleName: '', + roleKey: '', + menuCheckStrictly: true, + deptCheckStrictly: true, + remark: '', + dataScope: '1', + menuIds: [], + deptId: '', + isSpecial: null, + deptIds: [], + roleSource: '2' +}; + +const data = reactive({ + form: { ...initForm }, + queryParams: { + pageNum: 1, + pageSize: 10, + roleName: '', + roleKey: '', + deptId: '', + status: '', + roleSource: '2' + }, + rules: { + roleName: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }], + roleKey: [{ required: true, message: '权限字符不能为空', trigger: 'blur' }], + roleSort: [{ required: true, message: '角色顺序不能为空', trigger: 'blur' }] + } +}); +const { form, queryParams, rules } = toRefs(data); + +const dialog = reactive<DialogOption>({ + visible: false, + title: '' +}); + +/** 通过条件过滤节点 */ +const filterNode = (value: string, data: any) => { + if (!value) return true; + return data.label.indexOf(value) !== -1; +}; + +/** 节点单击事件 */ +const handleNodeClick = (data: DeptVO) => { + queryParams.value.deptId = data.id as string; + handleQuery(); +}; + +/** + * 查询角色列表 + */ +const getList = () => { + loading.value = true; + listRole(proxy?.addDateRange(queryParams.value, dateRange.value)).then((res) => { + roleList.value = res.rows; + total.value = res.total; + loading.value = false; + }); +}; + +/** + * 搜索按钮操作 + */ +const handleQuery = () => { + queryParams.value.pageNum = 1; + getList(); +}; + +/** 重置 */ +const resetQuery = () => { + dateRange.value = ['', '']; + queryFormRef.value?.resetFields(); + queryParams.value.pageNum = 1; + queryParams.value.deptId = undefined; + deptTreeRef.value?.setCurrentKey(undefined); + handleQuery(); +}; +/**删除按钮操作 */ +const handleDelete = async (row?: RoleVO) => { + const roleids = row?.roleId || ids.value; + await proxy?.$modal.confirm('是否确认删除角色编号为' + roleids + '数据项目'); + await delRole(roleids); + getList(); + proxy?.$modal.msgSuccess('删除成功'); +}; + +/** 导出按钮操作 */ +const handleExport = () => { + proxy?.download( + 'system/role/export', + { + ...queryParams.value + }, + `role_${new Date().getTime()}.xlsx` + ); +}; +/** 多选框选中数据 */ +const handleSelectionChange = (selection: RoleVO[]) => { + ids.value = selection.map((item: RoleVO) => item.roleId); + single.value = selection.length != 1; + multiple.value = !selection.length; +}; + +/** 角色状态修改 */ +const handleStatusChange = async (row: RoleVO) => { + let text = row.status === '0' ? '启用' : '停用'; + try { + await proxy?.$modal.confirm('确认要"' + text + '""' + row.roleName + '"角色吗?'); + await changeRoleStatus(row.roleId, row.status); + proxy?.$modal.msgSuccess(text + '成功'); + } catch { + row.status = row.status === '0' ? '1' : '0'; + } +}; + +/** 分配用户 */ +const handleAuthUser = (row: RoleVO) => { + router.push('/system/role-auth/user/' + row.roleId); +}; + +/** 查询菜单树结构 */ +const getMenuTreeselect = async () => { + const res = await menuTreeselect({ menuSource: '2' }); + menuOptions.value = res.data; +}; +/** 所有部门节点数据 */ +const getDeptAllCheckedKeys = (): any => { + // 目前被选中的部门节点 + let checkedKeys = deptRef.value?.getCheckedKeys(); + // 半选中的部门节点 + let halfCheckedKeys = deptRef.value?.getHalfCheckedKeys(); + if (halfCheckedKeys) { + checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys); + } + return checkedKeys; +}; +/** 重置新增的表单以及其他数据 */ +const reset = () => { + menuRef.value?.setCheckedKeys([]); + menuExpand.value = false; + menuNodeAll.value = false; + deptExpand.value = true; + deptNodeAll.value = false; + form.value = { ...initForm }; + roleFormRef.value?.resetFields(); +}; + +/** 添加角色 */ +const handleAdd = () => { + reset(); + getMenuTreeselect(); + dialog.visible = true; + dialog.title = '添加角色'; +}; +/** 修改角色 */ +const handleUpdate = async (row?: RoleVO) => { + reset(); + const roleId = row?.roleId || ids.value[0]; + const { data } = await getRole(roleId); + Object.assign(form.value, data); + form.value.roleSort = Number(form.value.roleSort); + const res = await getRoleMenuTreeselect(roleId); + dialog.title = '修改角色'; + dialog.visible = true; + res.checkedKeys.forEach((v) => { + nextTick(() => { + menuRef.value?.setChecked(v, true, false); + }); + }); +}; +/** 根据角色ID查询菜单树结构 */ +const getRoleMenuTreeselect = (roleId: string | number) => { + return roleMenuTreeselect(roleId, { menuSource: '2' }).then((res): RoleMenuTree => { + menuOptions.value = res.data.menus; + return res.data; + }); +}; +/** 根据角色ID查询部门树结构 */ +const getRoleDeptTreeSelect = async (roleId: string | number) => { + const res = await deptTreeSelect(roleId, { roleSource: '2' }); + deptOptions.value = res.data.depts; + return res.data; +}; +/** 树权限(展开/折叠)*/ +const handleCheckedTreeExpand = (value: boolean, type: string) => { + if (type == 'menu') { + let treeList = menuOptions.value; + for (let i = 0; i < treeList.length; i++) { + if (menuRef.value) { + menuRef.value.store.nodesMap[treeList[i].id].expanded = value; + } + } + } else if (type == 'dept') { + let treeList = deptOptions.value; + for (let i = 0; i < treeList.length; i++) { + if (deptRef.value) { + deptRef.value.store.nodesMap[treeList[i].id].expanded = value; + } + } + } +}; +/** 树权限(全选/全不选) */ +const handleCheckedTreeNodeAll = (value: any, type: string) => { + if (type == 'menu') { + menuRef.value?.setCheckedNodes(value ? (menuOptions.value as any) : []); + } else if (type == 'dept') { + deptRef.value?.setCheckedNodes(value ? (deptOptions.value as any) : []); + } +}; +/** 树权限(父子联动) */ +const handleCheckedTreeConnect = (value: any, type: string) => { + if (type == 'menu') { + form.value.menuCheckStrictly = value; + } else if (type == 'dept') { + form.value.deptCheckStrictly = value; + } +}; +/** 所有菜单节点数据 */ +const getMenuAllCheckedKeys = (): any => { + // 目前被选中的菜单节点 + let checkedKeys = menuRef.value?.getCheckedKeys(); + // 半选中的菜单节点 + let halfCheckedKeys = menuRef.value?.getHalfCheckedKeys(); + if (halfCheckedKeys) { + checkedKeys?.unshift.apply(checkedKeys, halfCheckedKeys); + } + return checkedKeys; +}; +/** 提交按钮 */ +const submitForm = () => { + roleFormRef.value?.validate(async (valid: boolean) => { + if (valid) { + form.value.menuIds = getMenuAllCheckedKeys(); + form.value.roleId ? await updateRole(form.value) : await addRole(form.value); + proxy?.$modal.msgSuccess('操作成功'); + dialog.visible = false; + getList(); + } + }); +}; +/** 取消按钮 */ +const cancel = () => { + reset(); + dialog.visible = false; +}; +/** 选择角色权限范围触发 */ +const dataScopeSelectChange = (value: string) => { + if (value !== '2') { + deptRef.value?.setCheckedKeys([]); + } +}; +/** 分配数据权限操作 */ +const handleDataScope = async (row: RoleVO) => { + const response = await getRole(row.roleId); + Object.assign(form.value, response.data); + const res = await getRoleDeptTreeSelect(row.roleId); + openDataScope.value = true; + dialog.title = '分配数据权限'; + await nextTick(() => { + deptRef.value?.setCheckedKeys(res.checkedKeys); + }); +}; +/** 提交按钮(数据权限) */ +const submitDataScope = async () => { + if (form.value.roleId) { + form.value.deptIds = getDeptAllCheckedKeys(); + await dataScope(form.value); + proxy?.$modal.msgSuccess('修改成功'); + openDataScope.value = false; + getList(); + } +}; +/** 取消按钮(数据权限)*/ +const cancelDataScope = () => { + dataScopeRef.value?.resetFields(); + form.value = { ...initForm }; + openDataScope.value = false; +}; +/** 查询部门下拉树结构 */ +const getDeptTree = async () => { + const res = await api.deptTreeSelect({ isShow: '1' }); + deptOptions.value = res.data; + enabledDeptOptions.value = filterDisabledDept(res.data); +}; + +/** 过滤禁用的部门 */ +const filterDisabledDept = (deptList: DeptTreeVO[]) => { + return deptList.filter((dept) => { + if (dept.disabled) { + return false; + } + if (dept.children && dept.children.length) { + dept.children = filterDisabledDept(dept.children); + } + return true; + }); +}; + +onMounted(() => { + getDeptTree(); // 初始化部门数据 + getList(); +}); +</script> diff --git a/src/views/system/appRole/selectUser.vue b/src/views/system/appRole/selectUser.vue new file mode 100644 index 0000000..e55120a --- /dev/null +++ b/src/views/system/appRole/selectUser.vue @@ -0,0 +1,132 @@ +<template> + <el-row> + <el-dialog v-model="visible" title="选择用户" width="800px" top="5vh" append-to-body> + <el-form ref="queryFormRef" :model="queryParams" :inline="true"> + <el-form-item label="用户名称" prop="userName"> + <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="手机号码" prop="phonenumber"> + <el-input v-model="queryParams.phonenumber" 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-row> + <el-table ref="tableRef" :data="userList" height="260px" @row-click="clickRow" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="55"></el-table-column> + <el-table-column label="用户名称" prop="userName" :show-overflow-tooltip="true" /> + <el-table-column label="用户昵称" prop="nickName" :show-overflow-tooltip="true" /> + <el-table-column label="邮箱" prop="email" :show-overflow-tooltip="true" /> + <el-table-column label="手机" prop="phonenumber" :show-overflow-tooltip="true" /> + <el-table-column label="状态" align="center" prop="status"> + <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" width="180"> + <template #default="scope"> + <span>{{ proxy.parseTime(scope.row.createTime) }}</span> + </template> + </el-table-column> + </el-table> + <pagination v-if="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" :total="total" @pagination="getList" /> + </el-row> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="handleSelectUser">确 定</el-button> + <el-button @click="visible = false">取 消</el-button> + </div> + </template> + </el-dialog> + </el-row> +</template> + +<script setup name="SelectUser" lang="ts"> +import { authUserSelectAll, unallocatedUserList } from '@/api/system/role'; +import { UserVO } from '@/api/system/user/types'; +import { UserQuery } from '@/api/system/user/types'; + +const props = defineProps({ + roleId: { + type: [Number, String], + required: true + } +}); + +const { proxy } = getCurrentInstance() as ComponentInternalInstance; +const { sys_normal_disable } = toRefs<any>(proxy?.useDict('sys_normal_disable')); + +const userList = ref<UserVO[]>([]); +const visible = ref(false); +const total = ref(0); +const userIds = ref<Array<string | number>>([]); + +const queryParams = reactive<UserQuery>({ + pageNum: 1, + pageSize: 10, + roleId: undefined, + userName: undefined, + phonenumber: undefined +}); + +const tableRef = ref<ElTableInstance>(); +const queryFormRef = ref<ElFormInstance>(); + +const show = () => { + queryParams.roleId = props.roleId; + getList(); + visible.value = true; +}; + +/** + * 选择行 + */ +const clickRow = (row: any) => { + // ele的bug + tableRef.value?.toggleRowSelection(row, false); +}; +/** 多选框选中数据 */ +const handleSelectionChange = (selection: UserVO[]) => { + userIds.value = selection.map((item: UserVO) => item.userId); +}; + +/** 查询数据 */ +const getList = async () => { + const res = await unallocatedUserList(queryParams); + userList.value = res.rows; + total.value = res.total; +}; +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.pageNum = 1; + getList(); +}; +/** 重置按钮操作 */ +const resetQuery = () => { + queryFormRef.value?.resetFields(); + getList(); +}; + +const emit = defineEmits(['ok']); +/**选择授权用户操作 */ +const handleSelectUser = async () => { + const roleId = queryParams.roleId; + const ids = userIds.value.join(','); + if (ids == '') { + proxy?.$modal.msgError('请选择要分配的用户'); + return; + } + await authUserSelectAll({ roleId, userIds: ids }); + proxy?.$modal.msgSuccess('分配成功'); + emit('ok'); + visible.value = false; +}; +// 暴露 +defineExpose({ + show +}); +</script> + +<style scoped></style> diff --git a/src/views/system/menu/index.vue b/src/views/system/menu/index.vue index 6c2f1ae..540bb57 100644 --- a/src/views/system/menu/index.vue +++ b/src/views/system/menu/index.vue @@ -34,6 +34,7 @@ </el-row> </template> + <!-- 关键修改:开启lazy模式,使用本地数据筛选子节点 --> <el-table ref="menuTableRef" v-loading="loading" @@ -41,6 +42,9 @@ row-key="menuId" :tree-props="{ children: 'children', hasChildren: 'hasChildren' }" :default-expand-all="isExpandAll" + lazy + :load="loadChildren" + :has-children="hasChildren" > <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"> @@ -77,7 +81,7 @@ </el-table> </el-card> - <el-dialog v-model="dialog.visible" :title="dialog.title" destroy-on-close append-to-bod width="750px"> + <el-dialog v-model="dialog.visible" :title="dialog.title" destroy-on-close append-to-body width="750px"> <el-form ref="menuFormRef" :model="form" :rules="rules" label-width="100px"> <el-row> <el-col :span="24"> @@ -103,7 +107,6 @@ </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> @@ -122,10 +125,9 @@ <template #label> <span> <el-tooltip content="选择是外链则路由地址需要以`http(s)://`开头" placement="top"> - <el-icon> - <question-filled /> - </el-icon> </el-tooltip - >是否外链 + <el-icon><question-filled /></el-icon> + </el-tooltip> + 是否外链 </span> </template> <el-radio-group v-model="form.isFrame"> @@ -139,9 +141,7 @@ <template #label> <span> <el-tooltip content="访问的路由地址,如:`user`,如外网地址需内链访问则以`http(s)://`开头" placement="top"> - <el-icon> - <question-filled /> - </el-icon> + <el-icon><question-filled /></el-icon> </el-tooltip> 路由地址 </span> @@ -154,9 +154,7 @@ <template #label> <span> <el-tooltip content="访问的组件路径,如:`system/user/index`,默认在`views`目录下" placement="top"> - <el-icon> - <question-filled /> - </el-icon> + <el-icon><question-filled /></el-icon> </el-tooltip> 组件路径 </span> @@ -170,9 +168,7 @@ <template #label> <span> <el-tooltip content="控制器中定义的权限字符,如:@SaCheckPermission('system:user:list')" placement="top"> - <el-icon> - <question-filled /> - </el-icon> + <el-icon><question-filled /></el-icon> </el-tooltip> 权限字符 </span> @@ -185,9 +181,7 @@ <template #label> <span> <el-tooltip content='访问路由的默认传递参数,如:`{"id": 1, "name": "ry"}`' placement="top"> - <el-icon> - <question-filled /> - </el-icon> + <el-icon><question-filled /></el-icon> </el-tooltip> 路由参数 </span> @@ -199,9 +193,7 @@ <template #label> <span> <el-tooltip content="选择是则会被`keep-alive`缓存,需要匹配组件的`name`和地址保持一致" placement="top"> - <el-icon> - <question-filled /> - </el-icon> + <el-icon><question-filled /></el-icon> </el-tooltip> 是否缓存 </span> @@ -217,9 +209,7 @@ <template #label> <span> <el-tooltip content="选择隐藏则路由将不会出现在侧边栏,但仍然可以访问" placement="top"> - <el-icon> - <question-filled /> - </el-icon> + <el-icon><question-filled /></el-icon> </el-tooltip> 显示状态 </span> @@ -234,9 +224,7 @@ <template #label> <span> <el-tooltip content="选择停用则路由将不会出现在侧边栏,也不能被访问" placement="top"> - <el-icon> - <question-filled /> - </el-icon> + <el-icon><question-filled /></el-icon> </el-tooltip> 菜单状态 </span> @@ -264,6 +252,20 @@ import { addMenu, delMenu, getMenu, listMenu, updateMenu } from '@/api/system/menu'; import { MenuForm, MenuQuery, MenuVO } from '@/api/system/menu/types'; import { MenuTypeEnum } from '@/enums/MenuTypeEnum'; +import { ElFormInstance, ElTableInstance } from 'element-plus'; +import { ComponentInternalInstance, getCurrentInstance, onMounted, ref, reactive, toRefs } from 'vue'; + +// 类型定义 +interface DialogOption { + visible: boolean; + title: string; +} + +interface PageData<T, U> { + form: T; + queryParams: U; + rules: Record<string, any[]>; +} interface MenuOptionsType { menuId: number; @@ -271,20 +273,29 @@ interface MenuOptionsType { 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')); +// 1. 存储完整数据的数组(一次性获取后本地保存) +const fullMenuList = ref<MenuVO[]>([]); +// 2. 子节点缓存(优化查询性能) +const childrenCache = ref<Record<number, MenuVO[]>>({}); + +// 页面状态 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 = { @@ -298,35 +309,114 @@ const initFormData = { isFrame: '1', isCache: '0', visible: '0', - status: '0' + status: '0', + menuSource: 1 }; -const data = reactive<PageData<MenuForm, MenuQuery>>({ +const data = reactive({ form: { ...initFormData }, queryParams: { menuName: undefined, - status: undefined + status: undefined, + menuSource: 1 }, rules: { menuName: [{ required: true, message: '菜单名称不能为空', trigger: 'blur' }], orderNum: [{ required: true, message: '菜单顺序不能为空', trigger: 'blur' }], - path: [{ required: true, message: '路由地址不能为空', trigger: 'blur' }] + path: [ + { + required: (() => data.form.menuType !== 'F') as any, + 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; - } + try { + // 1. 一次性获取所有数据 + const res = await listMenu(queryParams.value); + fullMenuList.value = res.data || []; - loading.value = false; + // 2. 构建缓存(提前计算所有节点的子节点) + buildChildrenCache(); + console.log(11111); + + // 3. 只展示根节点(parentId=0) + menuList.value = filterChildren(0); + } catch (err) { + proxy?.$modal.msgError('加载菜单失败'); + } finally { + loading.value = false; + } }; + +/** + * 构建子节点缓存 + * 一次性处理所有数据,建立父ID到子节点列表的映射 + */ +const buildChildrenCache = () => { + const cache: Record<number, MenuVO[]> = {}; + + // 初始化所有可能的父ID + fullMenuList.value.forEach((item) => { + if (!cache[item.parentId!]) { + cache[item.parentId!] = []; + } + }); + // 为每个父ID添加子节点 + fullMenuList.value.forEach((item) => { + if (cache[item.parentId!]) { + // 标记节点是否有子节点(用于显示展开图标) + const hasChildren = fullMenuList.value.some((child) => child.parentId == item.menuId); + cache[item.parentId!].push({ ...item, hasChildren }); + } + }); + // 按排序号排序子节点 + // Object.keys(cache).forEach((parentId) => { + // cache[Number(parentId)].sort((a, b) => (a.orderNum || 0) - (b.orderNum || 0)); + // }); + + childrenCache.value = cache; +}; + +/** + * 从缓存中筛选指定父节点的子节点 + * @param parentId 父节点ID + * @returns 子节点列表 + */ +const filterChildren = (parentId: number): MenuVO[] => { + return childrenCache.value[parentId] || []; +}; + +/** + * 懒加载子节点实现 + * 从本地缓存中获取子节点,不发起额外请求 + */ +const loadChildren = async (row, treeNode: any, resolve: (data: MenuVO[]) => void) => { + const parentId = row.menuId; + console.log(childrenCache.value); + // 从缓存获取子节点并返回 + resolve(filterChildren(parentId)); +}; + +/** + * 判断节点是否有子节点 + * @param row 节点数据 + * @returns 是否有子节点 + */ +const hasChildren = (row: MenuVO) => { + return row.hasChildren || false; +}; + /** 查询菜单下拉树结构 */ const getTreeselect = async () => { menuOptions.value = []; @@ -335,11 +425,13 @@ const getTreeselect = async () => { menu.children = proxy?.handleTree<MenuOptionsType>(response.data, 'menuId'); menuOptions.value.push(menu); }; + /** 取消按钮 */ const cancel = () => { reset(); dialog.visible = false; }; + /** 表单重置 */ const reset = () => { form.value = { ...initFormData }; @@ -350,11 +442,13 @@ const reset = () => { const handleQuery = () => { getList(); }; + /** 重置按钮操作 */ const resetQuery = () => { queryFormRef.value?.resetFields(); handleQuery(); }; + /** 新增按钮操作 */ const handleAdd = (row?: MenuVO) => { reset(); @@ -363,18 +457,24 @@ const handleAdd = (row?: MenuVO) => { 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); + // 如果有子节点,递归处理 + if (item.hasChildren) { + toggleExpandAll(filterChildren(item.menuId!), status); + } }); }; + /** 修改按钮操作 */ const handleUpdate = async (row: MenuVO) => { reset(); @@ -386,25 +486,38 @@ const handleUpdate = async (row: MenuVO) => { 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); + // 保存数据到后端 + if (form.value.menuId) { + await updateMenu(form.value); + } else { + 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(); }); diff --git a/src/views/system/role/index.vue b/src/views/system/role/index.vue index 89a185e..f7e01f5 100644 --- a/src/views/system/role/index.vue +++ b/src/views/system/role/index.vue @@ -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"> @@ -13,19 +12,11 @@ </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-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 label="创建时间" style="width: 308px"> - <el-date-picker v-model="dateRange" value-format="YYYY-MM-DD HH:mm:ss" type="daterange" - range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" - :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"></el-date-picker> - </el-form-item> - <el-form-item> - <el-button type="primary" icon="Search" @click="handleQuery" - v-hasPermi="['system:role:query']">搜索</el-button> + <el-button type="primary" icon="Search" @click="handleQuery" v-hasPermi="['system:role:query']">搜索</el-button> <el-button icon="Refresh" @click="resetQuery">重置</el-button> </el-form-item> </el-form> @@ -37,9 +28,18 @@ <el-col :lg="4" :xs="24" style=""> <el-card shadow="hover"> <el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable /> - <el-tree ref="deptTreeRef" class="mt-2" node-key="id" :data="deptOptions" - :props="{ label: 'label', children: 'children' }" :expand-on-click-node="false" - :filter-node-method="filterNode" highlight-current default-expand-all @node-click="handleNodeClick" /> + <el-tree + ref="deptTreeRef" + class="mt-2" + node-key="id" + :data="deptOptions" + :props="{ label: 'label', children: 'children' }" + :expand-on-click-node="false" + :filter-node-method="filterNode" + highlight-current + default-expand-all + @node-click="handleNodeClick" + /> </el-card> </el-col> <el-col :lg="20" :xs="24"> @@ -47,25 +47,21 @@ <template #header> <el-row :gutter="10"> <el-col :span="1.5"> - <el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" - @click="handleAdd()">新增</el-button> + <el-button v-hasPermi="['system:role:add']" type="primary" plain icon="Plus" @click="handleAdd()">新增</el-button> </el-col> <el-col :span="1.5"> - <el-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" - @click="handleUpdate()">修改</el-button> + <el-button v-hasPermi="['system:role:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()" + >修改</el-button + > </el-col> <el-col :span="1.5"> - <el-button v-hasPermi="['system:role:delete']" type="danger" plain :disabled="ids.length === 0" - @click="handleDelete()">删除</el-button> - </el-col> - <el-col :span="1.5"> - <el-button v-hasPermi="['system:role:export']" type="warning" plain icon="Download" - @click="handleExport">导出</el-button> + <el-button v-hasPermi="['system:role:delete']" type="danger" plain :disabled="ids.length === 0" @click="handleDelete()" + >删除</el-button + > </el-col> <right-toolbar v-model:show-search="showSearch" @query-table="getList"></right-toolbar> </el-row> </template> - <el-table ref="roleTableRef" v-loading="loading" :data="roleList" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="55" align="center" /> <el-table-column v-if="false" label="角色编号" prop="roleId" width="120" /> @@ -74,8 +70,7 @@ <el-table-column label="显示顺序" prop="roleSort" width="100" /> <el-table-column label="状态" align="center" width="100"> <template #default="scope"> - <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" - @change="handleStatusChange(scope.row)"></el-switch> + <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch> </template> </el-table-column> <el-table-column label="创建时间" align="center" prop="createTime"> @@ -87,27 +82,28 @@ <el-table-column fixed="right" label="操作" width="180"> <template #default="scope"> <el-tooltip v-if="scope.row.roleId !== 1" content="修改" placement="top"> - <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="Edit" - @click="handleUpdate(scope.row)"></el-button> + <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button> </el-tooltip> <el-tooltip v-if="scope.row.roleId !== 1" content="删除" placement="top"> - <el-button v-hasPermi="['system:role:remove']" link type="primary" icon="Delete" - @click="handleDelete(scope.row)"></el-button> + <el-button v-hasPermi="['system:role:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button> </el-tooltip> <el-tooltip v-if="scope.row.roleId !== 1" content="数据权限" placement="top"> - <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="CircleCheck" - @click="handleDataScope(scope.row)"></el-button> + <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="CircleCheck" @click="handleDataScope(scope.row)"></el-button> </el-tooltip> <el-tooltip v-if="scope.row.roleId !== 1" content="分配用户" placement="top"> - <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="User" - @click="handleAuthUser(scope.row)"></el-button> + <el-button v-hasPermi="['system:role:edit']" link type="primary" icon="User" @click="handleAuthUser(scope.row)"></el-button> </el-tooltip> </template> </el-table-column> </el-table> - <pagination v-if="total > 0" v-model:total="total" v-model:page="queryParams.pageNum" - v-model:limit="queryParams.pageSize" @pagination="getList" /> + <pagination + v-if="total > 0" + v-model:total="total" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + @pagination="getList" + /> </el-card> </el-col> </el-row> @@ -115,8 +111,15 @@ <el-dialog v-model="dialog.visible" :title="dialog.title" width="500px" append-to-body> <el-form ref="roleFormRef" :model="form" :rules="rules" label-width="110px"> <el-form-item label="所属部门" prop="deptId"> - <el-cascader :options="deptOptions" v-model="form.deptId" placeholder="请选择所属部门" clearable filterable - :show-all-levels="false" :props="{ value: 'id', emitPath: false, checkStrictly: true }" @change=""> + <el-cascader + :options="deptOptions" + v-model="form.deptId" + placeholder="请选择所属部门" + clearable + filterable + :show-all-levels="false" + :props="{ value: 'id', emitPath: false, checkStrictly: true }" + > </el-cascader> </el-form-item> <el-form-item label="角色名称" prop="roleName"> @@ -138,23 +141,26 @@ </el-form-item> <el-form-item label="状态"> <el-radio-group v-model="form.status"> - <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label - }}</el-radio> + <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }}</el-radio> </el-radio-group> </el-form-item> <el-form-item label="菜单权限"> - <el-checkbox v-model="menuExpand" - @change="handleCheckedTreeExpand(Boolean($event), 'menu')">展开/折叠</el-checkbox> + <el-checkbox v-model="menuExpand" @change="handleCheckedTreeExpand(Boolean($event), 'menu')">展开/折叠</el-checkbox> <el-checkbox v-model="menuNodeAll" @change="handleCheckedTreeNodeAll($event, 'menu')">全选/全不选</el-checkbox> - <el-checkbox v-model="form.menuCheckStrictly" - @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox> - <el-tree ref="menuRef" class="tree-border" :data="menuOptions" show-checkbox node-key="id" - :check-strictly="!form.menuCheckStrictly" empty-text="加载中,请稍候" - :props="{ label: 'label', children: 'children' }"></el-tree> + <el-checkbox v-model="form.menuCheckStrictly" @change="handleCheckedTreeConnect($event, 'menu')">父子联动</el-checkbox> + <el-tree + ref="menuRef" + class="tree-border" + :data="menuOptions" + show-checkbox + node-key="id" + :check-strictly="!form.menuCheckStrictly" + empty-text="加载中,请稍候" + :props="{ label: 'label', children: 'children' }" + ></el-tree> </el-form-item> <el-form-item label="是否为特殊角色"> - <el-switch v-model="form.isSpecial" active-value="1" inactive-value="0" active-text="是" inactive-text="否"> - </el-switch> + <el-switch v-model="form.isSpecial" active-value="1" inactive-value="0" active-text="是" inactive-text="否"> </el-switch> </el-form-item> <el-form-item label="备注"> <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input> @@ -179,19 +185,24 @@ </el-form-item> <el-form-item label="权限范围"> <el-select v-model="form.dataScope" @change="dataScopeSelectChange"> - <el-option v-for="item in dataScopeOptions" :key="item.value" :label="item.label" - :value="item.value"></el-option> + <el-option v-for="item in dataScopeOptions" :key="item.value" :label="item.label" :value="item.value"></el-option> </el-select> </el-form-item> <el-form-item v-show="form.dataScope === '2'" label="数据权限"> - <el-checkbox v-model="deptExpand" - @change="handleCheckedTreeExpand(Boolean($event), 'dept')">展开/折叠</el-checkbox> + <el-checkbox v-model="deptExpand" @change="handleCheckedTreeExpand(Boolean($event), 'dept')">展开/折叠</el-checkbox> <el-checkbox v-model="deptNodeAll" @change="handleCheckedTreeNodeAll($event, 'dept')">全选/全不选</el-checkbox> - <el-checkbox v-model="form.deptCheckStrictly" - @change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox> - <el-tree ref="deptRef" class="tree-border" :data="deptOptions" show-checkbox default-expand-all node-key="id" - :check-strictly="!form.deptCheckStrictly" empty-text="加载中,请稍候" - :props="{ label: 'label', children: 'children' }"></el-tree> + <el-checkbox v-model="form.deptCheckStrictly" @change="handleCheckedTreeConnect($event, 'dept')">父子联动</el-checkbox> + <el-tree + ref="deptRef" + class="tree-border" + :data="deptOptions" + show-checkbox + default-expand-all + node-key="id" + :check-strictly="!form.deptCheckStrictly" + empty-text="加载中,请稍候" + :props="{ label: 'label', children: 'children' }" + ></el-tree> </el-form-item> </el-form> <template #footer> @@ -252,7 +263,7 @@ const menuRef = ref<ElTreeInstance>(); const deptRef = ref<ElTreeInstance>(); const deptTreeRef = ref<ElTreeInstance>(); -const initForm: RoleForm = { +const initForm = { roleId: undefined, roleSort: 1, status: '0', @@ -265,10 +276,11 @@ const initForm: RoleForm = { menuIds: [], deptId: '', isSpecial: null, - deptIds: [] + deptIds: [], + roleSource: '1' }; -const data = reactive<PageData<RoleForm, RoleQuery>>({ +const data = reactive({ form: { ...initForm }, queryParams: { pageNum: 1, @@ -276,7 +288,8 @@ const data = reactive<PageData<RoleForm, RoleQuery>>({ roleName: '', roleKey: '', deptId: '', - status: '' + status: '', + roleSource: '1' }, rules: { roleName: [{ required: true, message: '角色名称不能为空', trigger: 'blur' }], @@ -377,7 +390,7 @@ const handleAuthUser = (row: RoleVO) => { /** 查询菜单树结构 */ const getMenuTreeselect = async () => { - const res = await menuTreeselect(); + const res = await menuTreeselect({ menuSource: '1' }); menuOptions.value = res.data; }; /** 所有部门节点数据 */ @@ -427,14 +440,14 @@ const handleUpdate = async (row?: RoleVO) => { }; /** 根据角色ID查询菜单树结构 */ const getRoleMenuTreeselect = (roleId: string | number) => { - return roleMenuTreeselect(roleId).then((res): RoleMenuTree => { + return roleMenuTreeselect(roleId, { menuSource: '1' }).then((res): RoleMenuTree => { menuOptions.value = res.data.menus; return res.data; }); }; /** 根据角色ID查询部门树结构 */ const getRoleDeptTreeSelect = async (roleId: string | number) => { - const res = await deptTreeSelect(roleId); + const res = await deptTreeSelect(roleId, { roleSource: '1' }); deptOptions.value = res.data.depts; return res.data; }; diff --git a/src/views/system/user/comm/editInfo.vue b/src/views/system/user/comm/editInfo.vue new file mode 100644 index 0000000..834e836 --- /dev/null +++ b/src/views/system/user/comm/editInfo.vue @@ -0,0 +1,258 @@ +<template> + <div class="p-2 editInfo"> + <el-form label-position="top" ref="userFormRef" :model="form" :rules="rules" label-width="80px" size="large"> + <el-row> + <el-col :span="10"> + <el-form-item label="用户昵称" prop="nickName"> + <el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" /> + </el-form-item> + </el-col> + <el-col :span="2"></el-col> + <el-col :span="10"> + <el-form-item label="归属部门" prop="deptId"> + <el-tree-select + v-model="form.deptId" + :data="enabledDeptOptions" + :props="{ value: 'id', label: 'label', children: 'children' }" + value-key="id" + placeholder="请选择归属部门" + check-strictly + @change="handleDeptChange" + /> + </el-form-item> + </el-col> + <el-col :span="10"> + <el-form-item label="手机号码" prop="phonenumber"> + <el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" /> + </el-form-item> </el-col + ><el-col :span="2"></el-col> + <el-col :span="10"> + <el-form-item label="邮箱" prop="email"> + <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" /> + </el-form-item> + </el-col> + <el-col :span="10"> + <el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName"> + <el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" /> + </el-form-item> </el-col + ><el-col :span="2"></el-col> + <el-col :span="10"> + <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password"> + <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password /> + </el-form-item> + </el-col> + <el-col :span="10"> + <el-form-item label="用户性别"> + <el-select v-model="form.sex" placeholder="请选择"> + <el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"></el-option> + </el-select> + </el-form-item> </el-col + ><el-col :span="2"></el-col> + <el-col :span="10"> + <el-form-item label="岗位"> + <el-select v-model="form.postIds" multiple placeholder="请选择"> + <el-option + v-for="item in postOptions" + :key="item.postId" + :label="item.postName" + :value="item.postId" + :disabled="item.status == '1'" + ></el-option> + </el-select> + </el-form-item> </el-col + ><el-col :span="2"></el-col> + <el-col :span="10"> + <el-form-item label="状态"> + <el-radio-group v-model="form.status"> + <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio> + </el-radio-group> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="备注"> + <el-input v-model="form.remark" type="textarea" :autosize="{ minRows: 3, maxRows: 3 }" placeholder="请输入内容"></el-input> + </el-form-item> + </el-col> + </el-row> + </el-form> + <div class="box_submit"> + <el-button size="large" @click="cancel()">取 消</el-button> + <el-button size="large" type="primary" @click="submitForm">确 定</el-button> + </div> + </div> +</template> + +<script setup name="Profile" lang="ts"> +import api from '@/api/system/user'; +import { listProject } from '@/api/project/project'; +import { getProjectByDeptId, getRoleList, optionselect } from '@/api/system/post'; +const { proxy } = getCurrentInstance() as ComponentInternalInstance; +const { sys_normal_disable, sys_user_sex } = toRefs<any>(proxy?.useDict('sys_normal_disable', 'sys_user_sex')); +const enabledDeptOptions = ref([]); +const deptOptions = ref([]); +const projectOptions = ref<any[]>([]); +const postOptions = ref([]); +const roleOptions = ref([]); +const userFormRef = ref<ElFormInstance>(); +const initFormData = { + userId: undefined, + deptId: undefined, + userName: '', + nickName: undefined, + password: '', + phonenumber: undefined, + email: undefined, + sex: undefined, + projectRoles: [ + { + projectId: '', + roleIds: [] + } + ], + status: '0', + remark: '', + postIds: [], + filePath: undefined +}; + +const initData = { + form: { ...initFormData }, + rules: { + userName: [ + { required: true, message: '用户名称不能为空', trigger: 'blur' }, + { + min: 2, + max: 20, + message: '用户名称长度必须介于 2 和 20 之间', + trigger: 'blur' + } + ], + nickName: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }], + password: [ + { required: true, message: '用户密码不能为空', trigger: 'blur' }, + { + min: 5, + max: 20, + message: '用户密码长度必须介于 5 和 20 之间', + trigger: 'blur' + }, + { pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\ |', trigger: 'blur' } + ], + email: [ + { + type: 'email', + message: '请输入正确的邮箱地址', + trigger: ['blur', 'change'] + } + ], + phonenumber: [ + { required: true, message: '请输入手机号码', trigger: 'blur' }, + { + pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, + message: '请输入正确的手机号码', + trigger: 'blur' + } + ] + } +}; +const data = reactive(initData); +const { form, rules } = toRefs(data); +/** 查询部门下拉树结构 */ +const getDeptTree = async () => { + const res = await api.deptTreeSelect({ isShow: '1' }); + deptOptions.value = res.data; + enabledDeptOptions.value = filterDisabledDept(res.data); + const projectList = await listProject(); + projectOptions.value = projectList.rows; +}; +/** 过滤禁用的部门 */ +const filterDisabledDept = (deptList) => { + return deptList.filter((dept) => { + if (dept.disabled) { + return false; + } + if (dept.children && dept.children.length) { + dept.children = filterDisabledDept(dept.children); + } + return true; + }); +}; +async function handleDeptChange(value: number | string) { + proxy?.$emit('setDeptId', value); + const response = await optionselect(value); + const roleList = await getRoleList(value); + roleOptions.value = roleList.data; + postOptions.value = response.data; + form.value.postIds = []; + form.value.projectRoles = [ + { + projectId: '', + roleIds: [] + } + ]; +} + +const submitForm = () => { + userFormRef.value?.validate(async (valid: boolean) => { + if (valid) { + form.value.userId ? await api.updateUser(form.value) : await api.addUser(form.value); + proxy?.$modal.msgSuccess('操作成功'); + proxy?.$emit('submit', false); + } + }); +}; + +const cancel = () => { + proxy?.$emit('close', false); +}; +/** 重置操作表单 */ +const reset = () => { + form.value = { ...initFormData }; + form.value.projectRoles = [ + { + projectId: '', + roleIds: [] + } + ]; + + userFormRef.value?.resetFields(); +}; +// 打开弹框调用参数 +const open = async (row?: any) => { + reset(); + if (row) { + // 编辑 + const { data } = await api.getUser(row.userId); + Object.assign(form.value, data.user); + postOptions.value = data.posts; + roleOptions.value = data.roles; + form.value.postIds = data.postIds; + form.value.projectRoles = data.projectRoles; + form.value.password = ''; + const roleList = await getRoleList(form.value.deptId); + roleOptions.value = roleList.data; + } else { + // 新增 + const { data } = await api.getUser(); + postOptions.value = data.posts; + } +}; +const getInfoForm = () => { + return form.value; +}; +onMounted(() => { + getDeptTree(); +}); +defineExpose({ open, getInfoForm }); +</script> +<style lang="scss" scoped> +.editInfo { + display: flex; + flex-direction: column; + .box_submit { + align-self: flex-end; + display: flex; + gap: 10px; + } +} +</style> diff --git a/src/views/system/user/comm/roleInfo.vue b/src/views/system/user/comm/roleInfo.vue new file mode 100644 index 0000000..74c7108 --- /dev/null +++ b/src/views/system/user/comm/roleInfo.vue @@ -0,0 +1,486 @@ +<template> + <div class="roleInfo"> + <div class="title_detail"> + <span>当前选定角色信息预览</span> + <div style="margin-top: 10px"> + <el-table :data="roleList" border height="150"> + <el-table-column label="所属部门" align="center" prop="deptName" /> + <el-table-column label="关联项目" align="center" prop="projectName" /> + <el-table-column label="web端担任角色" align="center" prop="webRoles" /> + <el-table-column label="APP端担任角色" align="center" prop="appRoles" /> + </el-table> + </div> + </div> + <div class="title_detail" style="margin-top: 10px"> + <span>选择或修改当前角色信息</span> + <div style="margin-top: 10px" class="box_detail"> + <!-- 项目列表选择区 --> + <div class="project_list"> + <span>关联项目模块</span> + <div class="project-items"> + <div + v-for="item in projectOptions" + :key="item.id" + class="project-item" + :class="{ 'project-item-selected': isProjectSelected(item.id) }" + @click="toggleProjectSelection(item)" + > + <div class="project-item-content"> + <el-checkbox v-model="item.checked" :value="item.id" class="project-checkbox" @change="handleProjectCheck(item, $event)" /> + <span class="project-name">{{ item.projectName }}</span> + </div> + </div> + </div> + </div> + <!-- 角色分配区 --> + <div class="post_list"> + <div class="list_title">关联项目角色分配</div> + <div v-if="selectedProjects.length === 0" class="no-selection">请从左侧选择项目进行角色分配</div> + <div v-for="(project, index) in selectedProjects" :key="project.id" class="project-role-container"> + <div class="project-header"> + <span class="project-title">{{ project.projectName }}</span> + <el-button type="text" class="remove-project" @click="removeProject(project.id)"> 移除 </el-button> + </div> + <div class="role-assignment"> + <div class="role-group"> + <label class="role-label">web端角色:</label> + <el-checkbox-group v-model="project.webRoles" @change="updateRoleList"> + <el-checkbox v-for="role in allRoles" :key="role.roleId" :value="role.roleId"> + {{ role.roleName }} + </el-checkbox> + </el-checkbox-group> + </div> + <div class="role-group"> + <label class="role-label">APP端角色:</label> + <el-checkbox-group v-model="project.appRoles" @change="updateRoleList"> + <el-checkbox v-for="role in AppRoles" :key="role.roleId" :value="role.roleId"> + {{ role.roleName }} + </el-checkbox> + </el-checkbox-group> + </div> + </div> + </div> + </div> + </div> + </div> + <div class="box_submit"> + <el-button size="large" @click="cancel()">取 消</el-button> + <el-button size="large" type="primary" @click="submitForm">保存角色信息</el-button> + </div> + </div> +</template> + +<script setup name="RoleProjectManagement" lang="ts"> +import { ref, reactive, toRefs, getCurrentInstance, ComponentInternalInstance, defineExpose, watch } from 'vue'; +import { ElFormInstance } from 'element-plus'; +import api from '@/api/system/user'; +import { listProject } from '@/api/project/project'; +import { getRoleList } from '@/api/system/post'; + +// 类型定义 +interface Project { + id: number | string; + projectName: string; + checked: boolean; + webRoles: string[]; + appRoles: string[]; +} + +interface Role { + roleId: number | string; + roleName: string; + roleSort?: number; +} + +interface RoleInfo { + userNick: string; + deptName: string; + postName: string; + projectName: string; + webRoles: string; + appRoles: string; +} + +// 组件实例 +const { proxy } = getCurrentInstance() as ComponentInternalInstance; + +// 响应式数据 +const projectOptions = ref<Project[]>([]); +const selectedProjects = ref<Project[]>([]); +const allRoles = ref<Role[]>([]); // web端角色 +const AppRoles = ref<Role[]>([]); // APP端角色 +const roleList = ref<RoleInfo[]>([]); +// 表单初始数据 +const initFormData = { + userId: undefined, + deptId: undefined, + userName: '', + nickName: undefined, + password: '', + phonenumber: undefined, + email: undefined, + sex: undefined, + projectRoles: [] as Array<{ projectId: number | string; webRoles: string[]; appRoles: string[] }>, + status: '0', + remark: '', + postIds: [], + filePath: undefined, + deptName: '' // 新增部门名称字段 +}; + +const data = reactive({ + form: { ...initFormData } +}); +const deptName = ref(''); +const { form } = toRefs(data); + +// 核心方法:更新预览列表 +const updateRoleList = () => { + if (selectedProjects.value.length === 0) { + roleList.value = []; + return; + } + roleList.value = selectedProjects.value.map((project) => { + // 处理web端角色名称 + var webRoleNames = project.webRoles + .map((roleId) => { + const role = allRoles.value.find((r) => r.roleId === roleId); + return role ? role.roleName : ''; + }) + .filter(Boolean); + + // 处理APP端角色名称 + var appRoleNames = project.appRoles + .map((roleId) => { + const role = AppRoles.value.find((r) => r.roleId === roleId); + return role ? role.roleName : ''; + }) + .filter(Boolean); + webRoleNames = [...new Set(webRoleNames)]; + appRoleNames = [...new Set(appRoleNames)]; + return { + deptName: deptName.value, + projectName: project.projectName, + webRoles: webRoleNames.length > 0 ? webRoleNames.join(',') : '无', + appRoles: appRoleNames.length > 0 ? appRoleNames.join(',') : '无' + }; + }); +}; + +// 监听已选项目变化,自动更新预览列表 +watch( + selectedProjects, + () => { + updateRoleList(); + }, + { deep: true } +); + +// 检查项目是否被选中 +const isProjectSelected = (projectId: number | string) => { + return selectedProjects.value.some((p) => p.id === projectId); +}; + +// 切换项目选择状态 +const toggleProjectSelection = (project: Project) => { + // handleProjectCheck(project, !project.checked); +}; + +// 处理项目勾选状态变化 +const handleProjectCheck = (project: Project, checked: boolean) => { + // project.checked = checked; + const index = selectedProjects.value.findIndex((p) => p.id === project.id); + + if (checked && index === -1) { + // 添加选中的项目 + selectedProjects.value.push({ + ...project, + webRoles: [], + appRoles: [] + }); + } else if (!checked && index !== -1) { + // 移除取消选中的项目 + selectedProjects.value.splice(index, 1); + } +}; + +// 移除项目 +const removeProject = (projectId: number | string) => { + // 更新选中项目列表 + selectedProjects.value = selectedProjects.value.filter((p) => p.id !== projectId); + + // 更新项目选项的勾选状态 + const project = projectOptions.value.find((p) => p.id === projectId); + if (project) { + project.checked = false; + } +}; + +// 提交表单 +const submitForm = async () => { + // 整理项目角色数据 + if (form.value.projectRoles.length == 0) { + proxy?.$modal.msgWarning('请选择项目角色'); + return; + } + form.value.projectRoles = selectedProjects.value.map((project) => ({ + projectId: project.id, + roleIds: [...new Set(project.webRoles), ...new Set(project.appRoles)] + })); + // 提交数据 + try { + if (form.value.userId) { + await api.updateUser(form.value); + } else { + await api.addUser(form.value); + } + proxy?.$modal.msgSuccess('操作成功'); + proxy?.$emit('submit', form.value); + cancel(); + } catch (error) { + proxy?.$modal.msgError('操作失败,请重试'); + console.error('提交失败:', error); + } +}; + +// 取消操作 +const cancel = () => { + resetForm(); + proxy?.$emit('close'); +}; + +// 重置表单 +const resetForm = () => { + data.form = { ...initFormData }; + projectOptions.value.forEach((p) => (p.checked = false)); + selectedProjects.value = []; + roleList.value = []; +}; + +// 初始化数据 +const initData = async () => { + try { + // 获取项目列表 + const projectRes = await listProject(); + projectOptions.value = projectRes.rows.map((item: any) => ({ + id: item.id, + projectName: item.projectName, + checked: false + })); + } catch (error) { + proxy?.$modal.msgError('数据加载失败'); + console.error('初始化数据失败:', error); + } +}; +// 打开弹窗时调用 +const open = async (row?: any, deptId?: any) => { + resetForm(); + await initData(); + deptName.value = row.deptName; + if (row) { + try { + if (!row.createTime) { + form.value = { ...row }; + // 获取角色列表 + if (form.value.deptId) { + deptId = form.value.deptId; + } + const roleRes = await getRoleList(deptId); + allRoles.value = roleRes.data.filter((item: Role) => item.roleSource == '1'); + AppRoles.value = roleRes.data.filter((item: Role) => item.roleSource != '1'); + } else { + // const { data } = await api.getUser(row.userId); + const data = row; + Object.assign(form.value, row); + // 获取角色列表 + if (form.value.deptId) { + deptId = form.value.deptId; + } + const roleRes = await getRoleList(deptId); + // 区分web端和app端角色 + allRoles.value = roleRes.data.filter((item: Role) => item.roleSource == '1'); + AppRoles.value = roleRes.data.filter((item: Role) => item.roleSource != '1'); + // 加载项目角色数据 + if (data.projectRoles && data.projectRoles.length) { + data.projectRoles.forEach((pr: any) => { + const project = projectOptions.value.find((p) => p.id === pr.projectId); + if (project) { + project.checked = true; + selectedProjects.value.push({ + ...project, + webRoles: pr.roleIds || [], + appRoles: pr.roleIds || [] + }); + } + }); + } + } + } catch (error) { + proxy?.$modal.msgError('加载用户数据失败'); + console.error('加载用户数据失败:', error); + } + } +}; + +// 暴露方法 +defineExpose({ open }); +</script> + +<style lang="scss"> +.roleInfo { + height: 100%; + padding-bottom: 60px; + box-sizing: border-box; + display: flex; + flex-direction: column; + gap: 12px; + + .title_detail { + > span { + font-size: 16px; + font-weight: 600; + color: #333; + display: inline-block; + padding-bottom: 5px; + border-bottom: 2px solid #409eff; + } + + .box_detail { + display: flex; + gap: 15px; + margin-top: 15px; + + > div { + height: 350px; + padding: 15px; + border-radius: 4px; + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + overflow-y: auto; + box-sizing: border-box; + } + + .project_list { + width: 320px; + border: 1px solid #eee; + + .project-items { + margin-top: 10px; + } + + .project-item { + padding: 10px 12px; + margin-bottom: 8px; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s ease; + border: 1px solid #eee; + + &:hover { + box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1); + } + + .project-item-content { + display: flex; + align-items: center; + } + + .project-checkbox { + margin-right: 8px; + } + + .project-name { + flex: 1; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; + } + } + + .project-item-selected { + background-color: #ecf5ff; + border-color: #c6e2ff; + } + } + + .post_list { + flex: 1; + border: 1px solid #eee; + + .list_title { + margin-bottom: 10px; + } + + .no-selection { + height: 100%; + display: flex; + align-items: center; + justify-content: center; + color: #909399; + font-size: 14px; + } + + .project-role-container { + padding: 15px; + margin-bottom: 15px; + // background-color: #f9f9f9; + border-radius: 4px; + border: 1px solid rgb(229 231 235); + + .project-header { + display: flex; + justify-content: space-between; + align-items: center; + margin-bottom: 15px; + + .project-title { + font-weight: 500; + color: #333; + font-size: 14px; + } + + .remove-project { + color: #f56c6c; + padding: 0 5px; + + &:hover { + color: #e4393c; + } + } + } + + .role-assignment { + .role-group { + display: flex; + flex-direction: column; + margin-bottom: 10px; + + .role-label { + display: inline-block; + width: 110px; + color: #606266; + font-size: 14px; + } + + .el-checkbox-group { + display: inline-block; + margin-left: 10px; + + .el-checkbox { + margin-right: 15px; + margin-bottom: 8px; + } + } + } + } + } + } + } + } + + .box_submit { + align-self: flex-end; + display: flex; + gap: 10px; + } +} +</style> diff --git a/src/views/system/user/index copy.vue b/src/views/system/user/index copy.vue new file mode 100644 index 0000000..87d6aef --- /dev/null +++ b/src/views/system/user/index copy.vue @@ -0,0 +1,788 @@ +<template> + <div class="p-2"> + <el-row :gutter="20"> + <!-- 部门树 --> + <el-col :lg="4" :xs="24" style=""> + <el-card shadow="hover"> + <el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable /> + <el-tree + ref="deptTreeRef" + class="mt-2" + node-key="id" + :data="deptOptions" + :props="{ label: 'label', children: 'children' }" + :expand-on-click-node="false" + :filter-node-method="filterNode" + highlight-current + default-expand-all + @node-click="handleNodeClick" + /> + </el-card> + </el-col> + <el-col :lg="20" :xs="24"> + <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="userName"> + <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" /> + </el-form-item> + <el-form-item label="手机号码" prop="phonenumber"> + <el-input v-model="queryParams.phonenumber" 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 label="创建时间" style="width: 308px"> + <el-date-picker + v-model="dateRange" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" + ></el-date-picker> + </el-form-item> + <el-form-item> + <el-button type="primary" icon="Search" @click="handleQuery" @v-has-permi="['system:user:query']">搜索</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-has-permi="['system:user:add']" type="primary" plain icon="Plus" @click="handleAdd()"> 新增 </el-button> + </el-col> + <el-col :span="1.5"> + <el-button v-has-permi="['system:user:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()"> + 修改 + </el-button> + </el-col> + <el-col :span="1.5"> + <el-button v-has-permi="['system:user:remove']" type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete()"> + 删除 + </el-button> + </el-col> + <el-col :span="1.5"> + <el-dropdown class="mt-[1px]"> + <el-button plain type="info"> + 更多 + <el-icon class="el-icon--right"> + <arrow-down /> + </el-icon> + </el-button> + <template #dropdown> + <el-dropdown-menu> + <el-dropdown-item icon="Download" @click="importTemplate">下载模板</el-dropdown-item> + <el-dropdown-item v-has-permi="['system:user:import']" icon="Top" @click="handleImport">导入数据 </el-dropdown-item> + <el-dropdown-item v-has-permi="['system:user:export']" icon="Download" @click="handleExport"> 导出数据 </el-dropdown-item> + </el-dropdown-menu> + </template> + </el-dropdown> + </el-col> + <right-toolbar v-model:show-search="showSearch" :columns="columns" :search="true" @query-table="getList"></right-toolbar> + </el-row> + </template> + + <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange"> + <el-table-column type="selection" width="50" align="center" /> + <el-table-column v-if="columns[0].visible" key="userId" label="用户编号" align="center" prop="userId" /> + <el-table-column v-if="columns[1].visible" key="userName" label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" /> + <el-table-column v-if="columns[2].visible" key="nickName" label="用户昵称" align="center" prop="nickName" :show-overflow-tooltip="true" /> + <el-table-column v-if="columns[3].visible" key="deptName" label="部门" align="center" prop="deptName" :show-overflow-tooltip="true" /> + <el-table-column v-if="columns[4].visible" key="phonenumber" label="手机号码" align="center" prop="phonenumber" width="120" /> + <el-table-column v-if="columns[5].visible" key="status" label="状态" align="center"> + <template #default="scope"> + <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch> + </template> + </el-table-column> + + <el-table-column v-if="columns[6].visible" label="创建时间" align="center" prop="createTime" width="160"> + <template #default="scope"> + <span>{{ scope.row.createTime }}</span> + </template> + </el-table-column> + + <el-table-column label="操作" fixed="right" width="230" class-name="small-padding fixed-width"> + <template #default="scope"> + <el-tooltip v-if="scope.row.userId !== 1" content="修改" placement="top"> + <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button> + </el-tooltip> + <el-tooltip v-if="scope.row.userId !== 1" content="删除" placement="top"> + <el-button v-hasPermi="['system:user:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button> + </el-tooltip> + + <el-tooltip v-if="scope.row.userId !== 1" content="重置密码" placement="top"> + <el-button v-hasPermi="['system:user:resetPwd']" link type="primary" icon="Key" @click="handleResetPwd(scope.row)"></el-button> + </el-tooltip> + + <el-tooltip v-if="scope.row.userId !== 1" content="分配角色" placement="top"> + <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)"></el-button> + </el-tooltip> + <el-tooltip v-if="scope.row.userId !== 1" content="编辑关联项目" placement="top"> + <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Edit" @click="handleUpdateProject(scope.row)"></el-button> + </el-tooltip> + <el-tooltip v-if="scope.row.userId !== 1" content="上传证书目录" placement="top"> + <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Upload" @click="handleUploadCert(scope.row)"></el-button> + </el-tooltip> + </template> + </el-table-column> + </el-table> + <el-dialog v-model="shuttleVisible" title="编辑关联项目" width="auto" destroy-on-close> + <shuttle-frame :userId="selectedUserId" @close="shuttleVisible = false" /> + </el-dialog> + + <pagination + v-show="total > 0" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + :total="total" + @pagination="getList" + /> + </el-card> + </el-col> + </el-row> + + <!-- 添加或修改用户配置对话框 --> + <el-dialog draggable ref="formDialogRef" v-model="dialog.visible" :title="dialog.title" width="600px" append-to-body @close="closeDialog"> + <el-form ref="userFormRef" :model="form" :rules="rules" label-width="80px"> + <el-row> + <el-col :span="12"> + <el-form-item label="用户昵称" prop="nickName"> + <el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="归属部门" prop="deptId"> + <el-tree-select + v-model="form.deptId" + :data="enabledDeptOptions" + :props="{ value: 'id', label: 'label', children: 'children' }" + value-key="id" + placeholder="请选择归属部门" + check-strictly + @change="handleDeptChange" + /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="手机号码" prop="phonenumber"> + <el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="邮箱" prop="email"> + <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName"> + <el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password"> + <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password /> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="用户性别"> + <el-select v-model="form.sex" placeholder="请选择"> + <el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" :value="dict.value"></el-option> + </el-select> + </el-form-item> + </el-col> + <el-col :span="12"> + <el-form-item label="岗位"> + <el-select v-model="form.postIds" multiple placeholder="请选择"> + <el-option + v-for="item in postOptions" + :key="item.postId" + :label="item.postName" + :value="item.postId" + :disabled="item.status == '1'" + ></el-option> + </el-select> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-row :gutter="20" v-for="(item, index) in form.projectRoles"> + <el-col :span="11" :offset="0"> + <el-form-item label="项目列表"> + <el-select v-model="item.projectId" placeholder="请选择"> + <el-option v-for="dict in projectOptions" :key="dict.id" :label="dict.shortName" :value="dict.id"></el-option> + </el-select> + </el-form-item> + </el-col> + <el-col :span="11" :offset="0"> + <el-form-item label="角色"> + <el-select v-model="item.roleIds" filterable multiple placeholder="请选择"> + <el-option + v-for="item in roleOptions" + :key="item.roleId" + :label="item.roleName" + :value="item.roleId" + :disabled="item.status == '1'" + ></el-option> + </el-select> + </el-form-item> + </el-col> + <el-col :span="1" :offset="0"> + <el-button type="primary" circle icon="Plus" @click="handleAddProject" v-if="index == 0"></el-button> + <el-button type="danger" circle icon="Delete" @click="delProject(index)" v-else></el-button> + </el-col> + </el-row> + </el-col> + <el-col :span="12"> + <el-form-item label="状态"> + <el-radio-group v-model="form.status"> + <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }} </el-radio> + </el-radio-group> + </el-form-item> + </el-col> + <el-col :span="24"> + <el-form-item label="备注"> + <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input> + </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> + + <!-- 用户导入对话框 --> + <el-dialog draggable v-model="upload.open" :title="upload.title" width="400px" append-to-body> + <el-upload + ref="uploadRef" + :limit="1" + accept=".xlsx, .xls" + :headers="upload.headers" + :action="upload.url + '?updateSupport=' + upload.updateSupport" + :disabled="upload.isUploading" + :on-progress="handleFileUploadProgress" + :on-success="handleFileSuccess" + :auto-upload="false" + drag + > + <el-icon class="el-icon--upload"> + <i-ep-upload-filled /> + </el-icon> + <div class="el-upload__text">将文件拖到此处,或<em>点击上传</em></div> + <template #tip> + <div class="text-center el-upload__tip"> + <div class="el-upload__tip"> + <el-checkbox v-model="upload.updateSupport" /> + 是否更新已经存在的用户数据 + </div> + <span>仅允许导入xls、xlsx格式文件。</span> + <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板 </el-link> + </div> + </template> + </el-upload> + <template #footer> + <div class="dialog-footer"> + <el-button type="primary" @click="submitFileForm">确 定</el-button> + <el-button @click="upload.open = false">取 消</el-button> + </div> + </template> + </el-dialog> + + <el-dialog title="上传证书目录" v-model="certDialog" width="30%" destroy-on-close> + <!-- <File-upload v-model="fileUpload" :limit="5"></File-upload> --> + <ImageUpload v-model="fileUpload" :limit="5"></ImageUpload> + <template #footer> + <span> + <el-button @click="certDialog = false">取消</el-button> + <el-button type="primary" @click="uploadCert">确定</el-button> + </span> + </template> + </el-dialog> + </div> +</template> + +<script setup name="User" lang="ts"> +import api, { uploadCertList } from '@/api/system/user'; +import { UserForm, UserQuery, UserVO } from '@/api/system/user/types'; +import { DeptTreeVO, DeptVO } from '@/api/system/dept/types'; +import { RoleVO } from '@/api/system/role/types'; +import { PostVO } from '@/api/system/post/types'; +import { globalHeaders } from '@/utils/request'; +import { to } from 'await-to-js'; +import { getProjectByDeptId, getRoleList, optionselect } from '@/api/system/post'; +import ShuttleFrame from '../../project/projectRelevancy/component/ShuttleFrame.vue'; +import { listProject } from '@/api/project/project'; +import editInfo from './comm/editInfo.vue'; +const router = useRouter(); +const { proxy } = getCurrentInstance() as ComponentInternalInstance; +const { sys_normal_disable, sys_user_sex } = toRefs<any>(proxy?.useDict('sys_normal_disable', 'sys_user_sex')); +const userList = ref<UserVO[]>(); +const loading = ref(true); +const showSearch = ref(true); +const ids = ref<Array<number | string>>([]); +const single = ref(true); +const multiple = ref(true); +const total = ref(0); +const dateRange = ref<[DateModelType, DateModelType]>(['', '']); +const deptName = ref(''); +const deptOptions = ref<DeptTreeVO[]>([]); +const enabledDeptOptions = ref<DeptTreeVO[]>([]); +const initPassword = ref<string>(''); +const postOptions = ref<PostVO[]>([]); +const roleOptions = ref<RoleVO[]>([]); +const projectOptions = ref<any[]>([]); + +/*** 用户导入参数 */ +const upload = reactive<ImportOption>({ + // 是否显示弹出层(用户导入) + open: false, + // 弹出层标题(用户导入) + title: '', + // 是否禁用上传 + isUploading: false, + // 是否更新已经存在的用户数据 + updateSupport: 0, + // 设置上传的请求头部 + headers: globalHeaders(), + // 上传的地址 + url: import.meta.env.VITE_APP_BASE_API + '/system/user/importData' +}); +// 列显隐信息 +const columns = ref<FieldOption[]>([ + { key: 0, label: `用户编号`, visible: false, children: [] }, + { key: 1, label: `用户名称`, visible: true, children: [] }, + { key: 2, label: `用户昵称`, visible: true, children: [] }, + { key: 3, label: `部门`, visible: true, children: [] }, + { key: 4, label: `手机号码`, visible: true, children: [] }, + { key: 5, label: `状态`, visible: true, children: [] }, + { key: 6, label: `创建时间`, visible: true, children: [] } +]); + +const deptTreeRef = ref<ElTreeInstance>(); +const queryFormRef = ref<ElFormInstance>(); +const userFormRef = ref<ElFormInstance>(); +const uploadRef = ref<ElUploadInstance>(); +const formDialogRef = ref<ElDialogInstance>(); + +const dialog = reactive<DialogOption>({ + visible: false, + title: '' +}); + +const initFormData: UserForm = { + userId: undefined, + deptId: undefined, + userName: '', + nickName: undefined, + password: '', + phonenumber: undefined, + email: undefined, + sex: undefined, + projectRoles: [ + { + projectId: '', + roleIds: [] + } + ], + status: '0', + remark: '', + postIds: [], + filePath: undefined +}; + +const initData: PageData<UserForm, UserQuery> = { + form: { ...initFormData }, + queryParams: { + pageNum: 1, + pageSize: 10, + userName: '', + phonenumber: '', + status: '', + deptId: '', + roleId: '' + }, + rules: { + userName: [ + { required: true, message: '用户名称不能为空', trigger: 'blur' }, + { + min: 2, + max: 20, + message: '用户名称长度必须介于 2 和 20 之间', + trigger: 'blur' + } + ], + nickName: [{ required: true, message: '用户昵称不能为空', trigger: 'blur' }], + password: [ + { required: true, message: '用户密码不能为空', trigger: 'blur' }, + { + min: 5, + max: 20, + message: '用户密码长度必须介于 5 和 20 之间', + trigger: 'blur' + }, + { pattern: /^[^<>"'|\\]+$/, message: '不能包含非法字符:< > " \' \\ |', trigger: 'blur' } + ], + email: [ + { + type: 'email', + message: '请输入正确的邮箱地址', + trigger: ['blur', 'change'] + } + ], + phonenumber: [ + { required: true, message: '请输入手机号码', trigger: 'blur' }, + { + pattern: /^1[3|4|5|6|7|8|9][0-9]\d{8}$/, + message: '请输入正确的手机号码', + trigger: 'blur' + } + ] + } +}; +const data = reactive<PageData<UserForm, UserQuery>>(initData); + +const { queryParams, form, rules } = toRefs<PageData<UserForm, UserQuery>>(data); + +/** 通过条件过滤节点 */ +const filterNode = (value: string, data: any) => { + if (!value) return true; + return data.label.indexOf(value) !== -1; +}; +/** 根据名称筛选部门树 */ +watchEffect( + () => { + deptTreeRef.value?.filter(deptName.value); + }, + { + flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行 + } +); + +/** 查询用户列表 */ +const getList = async () => { + loading.value = true; + const res = await api.listUser(proxy?.addDateRange(queryParams.value, dateRange.value)); + loading.value = false; + userList.value = res.rows; + total.value = res.total; +}; + +/** 查询部门下拉树结构 */ +const getDeptTree = async () => { + const res = await api.deptTreeSelect({ isShow: '1' }); + deptOptions.value = res.data; + enabledDeptOptions.value = filterDisabledDept(res.data); + const projectList = await listProject(); + projectOptions.value = projectList.rows; +}; + +/** 过滤禁用的部门 */ +const filterDisabledDept = (deptList: DeptTreeVO[]) => { + return deptList.filter((dept) => { + if (dept.disabled) { + return false; + } + if (dept.children && dept.children.length) { + dept.children = filterDisabledDept(dept.children); + } + return true; + }); +}; + +/** 节点单击事件 */ +const handleNodeClick = (data: DeptVO) => { + queryParams.value.deptId = data.id; + handleQuery(); +}; + +/** 部门选择变化 */ +const handleAddProject = () => { + form.value.projectRoles.push({ + projectId: '', + roleIds: [] + }); +}; + +/** 删除项目 */ +const delProject = (index: number) => { + form.value.projectRoles.splice(index, 1); +}; + +/** 搜索按钮操作 */ +const handleQuery = () => { + queryParams.value.pageNum = 1; + getList(); +}; +/** 重置按钮操作 */ +const resetQuery = () => { + dateRange.value = ['', '']; + queryFormRef.value?.resetFields(); + queryParams.value.pageNum = 1; + queryParams.value.deptId = undefined; + deptTreeRef.value?.setCurrentKey(undefined); + handleQuery(); +}; + +/** 删除按钮操作 */ +const handleDelete = async (row?: UserVO) => { + const userIds = row?.userId || ids.value; + const [err] = await to(proxy?.$modal.confirm('是否确认删除用户编号为"' + userIds + '"的数据项?') as any); + if (!err) { + await api.delUser(userIds); + await getList(); + proxy?.$modal.msgSuccess('删除成功'); + } +}; + +/** 用户状态修改 */ +const handleStatusChange = async (row: UserVO) => { + let text = row.status === '0' ? '启用' : '停用'; + try { + await proxy?.$modal.confirm('确认要"' + text + '""' + row.userName + '"用户吗?'); + await api.changeUserStatus(row.userId, row.status); + proxy?.$modal.msgSuccess(text + '成功'); + } catch (err) { + row.status = row.status === '0' ? '1' : '0'; + } +}; +/** 跳转角色分配 */ +const handleAuthRole = (row: UserVO) => { + const userId = row.userId; + router.push('/system/user-auth/role/' + userId); +}; + +/** 重置密码按钮操作 */ +const handleResetPwd = async (row: UserVO) => { + const [err, res] = await to( + ElMessageBox.prompt('请输入"' + row.userName + '"的新密码', '提示', { + confirmButtonText: '确定', + cancelButtonText: '取消', + closeOnClickModal: false, + inputPattern: /^.{5,20}$/, + inputErrorMessage: '用户密码长度必须介于 5 和 20 之间', + inputValidator: (value) => { + if (/<|>|"|'|\||\\/.test(value)) { + return '不能包含非法字符:< > " \' \\ |'; + } + } + }) + ); + if (!err && res) { + await api.resetUserPwd(row.userId, res.value); + proxy?.$modal.msgSuccess('修改成功,新密码是:' + res.value); + } +}; + +/** 选择条数 */ +const handleSelectionChange = (selection: UserVO[]) => { + ids.value = selection.map((item) => item.userId); + single.value = selection.length != 1; + multiple.value = !selection.length; +}; + +/** 导入按钮操作 */ +const handleImport = () => { + upload.title = '用户导入'; + upload.open = true; +}; +/** 导出按钮操作 */ +const handleExport = () => { + proxy?.download( + 'system/user/export', + { + ...queryParams.value + }, + `user_${new Date().getTime()}.xlsx` + ); +}; +/** 下载模板操作 */ +const importTemplate = () => { + proxy?.download('system/user/importTemplate', {}, `user_template_${new Date().getTime()}.xlsx`); +}; + +/**文件上传中处理 */ +const handleFileUploadProgress = () => { + upload.isUploading = true; +}; +/** 文件上传成功处理 */ +const handleFileSuccess = (response: any, file: UploadFile) => { + upload.open = false; + upload.isUploading = false; + uploadRef.value?.handleRemove(file); + ElMessageBox.alert("<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + '</div>', '导入结果', { + dangerouslyUseHTMLString: true + }); + getList(); +}; + +/** 提交上传文件 */ +function submitFileForm() { + uploadRef.value?.submit(); +} + +/** 重置操作表单 */ +const reset = () => { + form.value = { ...initFormData }; + form.value.projectRoles = [ + { + projectId: '', + roleIds: [] + } + ]; + + userFormRef.value?.resetFields(); +}; +/** 取消按钮 */ +const cancel = () => { + dialog.visible = false; + reset(); +}; + +/** 新增按钮操作 */ +const handleAdd = async () => { + reset(); + const { data } = await api.getUser(); + dialog.visible = true; + dialog.title = '新增用户'; + postOptions.value = data.posts; + form.value.password = initPassword.value.toString(); +}; + +/** 修改按钮操作 */ +const handleUpdate = async (row?: UserForm) => { + reset(); + const userId = row?.userId || ids.value[0]; + const { data } = await api.getUser(userId); + dialog.visible = true; + dialog.title = '修改用户'; + Object.assign(form.value, data.user); + postOptions.value = data.posts; + roleOptions.value = data.roles; + form.value.postIds = data.postIds; + form.value.projectRoles = data.projectRoles; + form.value.password = ''; + const roleList = await getRoleList(form.value.deptId); + + roleOptions.value = roleList.data; +}; + +const validate = () => { + for (let i = 0; i < form.value.projectRoles.length; i++) { + const item = form.value.projectRoles[i]; + if (!item.projectId || item.projectId.length === 0) { + proxy?.$modal.msgError(`第 ${i + 1} 行“项目列表”未填写`); + return false; // 阻止提交 + } + if (!item.roleIds || item.roleIds.length === 0) { + proxy?.$modal.msgError(`第 ${i + 1} 行“角色”未填写`); + return false; // 阻止提交 + } + } + return true; +}; + +/** 提交按钮 */ +const submitForm = () => { + const isValid = validate(); + if (!isValid) return; + userFormRef.value?.validate(async (valid: boolean) => { + if (valid) { + form.value.userId ? await api.updateUser(form.value) : await api.addUser(form.value); + proxy?.$modal.msgSuccess('操作成功'); + dialog.visible = false; + await getList(); + } + }); +}; + +/** + * 关闭用户弹窗 + */ +const closeDialog = () => { + dialog.visible = false; + resetForm(); +}; + +/** + * 重置表单 + */ +const resetForm = () => { + userFormRef.value?.resetFields(); + userFormRef.value?.clearValidate(); + + form.value.id = undefined; + form.value.status = '1'; +}; +onMounted(() => { + getDeptTree(); // 初始化部门数据 + getList(); // 初始化列表数据 + proxy?.getConfigKey('sys.user.initPassword').then((response) => { + initPassword.value = response.data; + }); +}); + +async function handleDeptChange(value: number | string) { + const response = await optionselect(value); + const roleList = await getRoleList(value); + + roleOptions.value = roleList.data; + postOptions.value = response.data; + form.value.postIds = []; + form.value.projectRoles = [ + { + projectId: [], + roleIds: [] + } + ]; +} + +const shuttleVisible = ref(false); +const selectedUserId = ref<number>(); +const handleUpdateProject = (row) => { + if (row) { + selectedUserId.value = row.userId; + shuttleVisible.value = true; + } +}; + +const certDialog = ref(false); +const certId = ref<string | number>(undefined); +const fileUpload = ref<string>(); +//上传证书 +const handleUploadCert = (row: UserVO) => { + certId.value = row.userId; + fileUpload.value = row.filePath; + certDialog.value = true; +}; + +const uploadCert = async () => { + if (!fileUpload.value) { + proxy?.$modal.msgError('请上传证书目录'); + return; + } + let res = await uploadCertList({ + userId: certId.value, + fileId: fileUpload.value + }); + console.log(res); + + certDialog.value = false; + proxy?.$modal.msgSuccess('上传证书目录成功'); +}; +</script> + +<style lang="scss" scoped></style> diff --git a/src/views/system/user/index.vue b/src/views/system/user/index.vue index d8c00ac..2a67330 100644 --- a/src/views/system/user/index.vue +++ b/src/views/system/user/index.vue @@ -5,14 +5,22 @@ <el-col :lg="4" :xs="24" style=""> <el-card shadow="hover"> <el-input v-model="deptName" placeholder="请输入部门名称" prefix-icon="Search" clearable /> - <el-tree ref="deptTreeRef" class="mt-2" node-key="id" :data="deptOptions" - :props="{ label: 'label', children: 'children' }" :expand-on-click-node="false" - :filter-node-method="filterNode" highlight-current default-expand-all @node-click="handleNodeClick" /> + <el-tree + ref="deptTreeRef" + class="mt-2" + node-key="id" + :data="deptOptions" + :props="{ label: 'label', children: 'children' }" + :expand-on-click-node="false" + :filter-node-method="filterNode" + highlight-current + default-expand-all + @node-click="handleNodeClick" + /> </el-card> </el-col> <el-col :lg="20" :xs="24"> - <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"> @@ -20,24 +28,27 @@ <el-input v-model="queryParams.userName" placeholder="请输入用户名称" clearable @keyup.enter="handleQuery" /> </el-form-item> <el-form-item label="手机号码" prop="phonenumber"> - <el-input v-model="queryParams.phonenumber" placeholder="请输入手机号码" clearable - @keyup.enter="handleQuery" /> + <el-input v-model="queryParams.phonenumber" 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-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 label="创建时间" style="width: 308px"> - <el-date-picker v-model="dateRange" value-format="YYYY-MM-DD HH:mm:ss" type="daterange" - range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期" - :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"></el-date-picker> + <el-date-picker + v-model="dateRange" + value-format="YYYY-MM-DD HH:mm:ss" + type="daterange" + range-separator="-" + start-placeholder="开始日期" + end-placeholder="结束日期" + :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]" + ></el-date-picker> </el-form-item> <el-form-item> - <el-button type="primary" icon="Search" @click="handleQuery" - @v-has-permi="['system:user:query']">搜索</el-button> + <el-button type="primary" icon="Search" @click="handleQuery" @v-has-permi="['system:user:query']">搜索</el-button> <el-button icon="Refresh" @click="resetQuery">重置</el-button> </el-form-item> @@ -50,18 +61,15 @@ <template #header> <el-row :gutter="10"> <el-col :span="1.5"> - <el-button v-has-permi="['system:user:add']" type="primary" plain icon="Plus" @click="handleAdd()"> 新增 - </el-button> + <el-button v-has-permi="['system:user:add']" type="primary" plain icon="Plus" @click="handleAdd()"> 新增 </el-button> </el-col> <el-col :span="1.5"> - <el-button v-has-permi="['system:user:edit']" type="success" plain :disabled="single" icon="Edit" - @click="handleUpdate()"> + <el-button v-has-permi="['system:user:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()"> 修改 </el-button> </el-col> <el-col :span="1.5"> - <el-button v-has-permi="['system:user:remove']" type="danger" plain :disabled="multiple" icon="Delete" - @click="handleDelete()"> + <el-button v-has-permi="['system:user:remove']" type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete()"> 删除 </el-button> </el-col> @@ -76,34 +84,26 @@ <template #dropdown> <el-dropdown-menu> <el-dropdown-item icon="Download" @click="importTemplate">下载模板</el-dropdown-item> - <el-dropdown-item v-has-permi="['system:user:import']" icon="Top" @click="handleImport">导入数据 - </el-dropdown-item> - <el-dropdown-item v-has-permi="['system:user:export']" icon="Download" @click="handleExport"> 导出数据 - </el-dropdown-item> + <el-dropdown-item v-has-permi="['system:user:import']" icon="Top" @click="handleImport">导入数据 </el-dropdown-item> + <el-dropdown-item v-has-permi="['system:user:export']" icon="Download" @click="handleExport"> 导出数据 </el-dropdown-item> </el-dropdown-menu> </template> </el-dropdown> </el-col> - <right-toolbar v-model:show-search="showSearch" :columns="columns" :search="true" - @query-table="getList"></right-toolbar> + <right-toolbar v-model:show-search="showSearch" :columns="columns" :search="true" @query-table="getList"></right-toolbar> </el-row> </template> <el-table v-loading="loading" :data="userList" @selection-change="handleSelectionChange"> <el-table-column type="selection" width="50" align="center" /> <el-table-column v-if="columns[0].visible" key="userId" label="用户编号" align="center" prop="userId" /> - <el-table-column v-if="columns[1].visible" key="userName" label="用户名称" align="center" prop="userName" - :show-overflow-tooltip="true" /> - <el-table-column v-if="columns[2].visible" key="nickName" label="用户昵称" align="center" prop="nickName" - :show-overflow-tooltip="true" /> - <el-table-column v-if="columns[3].visible" key="deptName" label="部门" align="center" prop="deptName" - :show-overflow-tooltip="true" /> - <el-table-column v-if="columns[4].visible" key="phonenumber" label="手机号码" align="center" prop="phonenumber" - width="120" /> + <el-table-column v-if="columns[1].visible" key="userName" label="用户名称" align="center" prop="userName" :show-overflow-tooltip="true" /> + <el-table-column v-if="columns[2].visible" key="nickName" label="用户昵称" align="center" prop="nickName" :show-overflow-tooltip="true" /> + <el-table-column v-if="columns[3].visible" key="deptName" label="部门" align="center" prop="deptName" :show-overflow-tooltip="true" /> + <el-table-column v-if="columns[4].visible" key="phonenumber" label="手机号码" align="center" prop="phonenumber" width="120" /> <el-table-column v-if="columns[5].visible" key="status" label="状态" align="center"> <template #default="scope"> - <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" - @change="handleStatusChange(scope.row)"></el-switch> + <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch> </template> </el-table-column> @@ -116,30 +116,24 @@ <el-table-column label="操作" fixed="right" width="230" class-name="small-padding fixed-width"> <template #default="scope"> <el-tooltip v-if="scope.row.userId !== 1" content="修改" placement="top"> - <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Edit" - @click="handleUpdate(scope.row)"></el-button> + <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Edit" @click="handleUpdate(scope.row)"></el-button> </el-tooltip> <el-tooltip v-if="scope.row.userId !== 1" content="删除" placement="top"> - <el-button v-hasPermi="['system:user:remove']" link type="primary" icon="Delete" - @click="handleDelete(scope.row)"></el-button> + <el-button v-hasPermi="['system:user:remove']" link type="primary" icon="Delete" @click="handleDelete(scope.row)"></el-button> </el-tooltip> <el-tooltip v-if="scope.row.userId !== 1" content="重置密码" placement="top"> - <el-button v-hasPermi="['system:user:resetPwd']" link type="primary" icon="Key" - @click="handleResetPwd(scope.row)"></el-button> + <el-button v-hasPermi="['system:user:resetPwd']" link type="primary" icon="Key" @click="handleResetPwd(scope.row)"></el-button> </el-tooltip> <el-tooltip v-if="scope.row.userId !== 1" content="分配角色" placement="top"> - <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="CircleCheck" - @click="handleAuthRole(scope.row)"></el-button> + <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="CircleCheck" @click="handleAuthRole(scope.row)"></el-button> </el-tooltip> <el-tooltip v-if="scope.row.userId !== 1" content="编辑关联项目" placement="top"> - <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Edit" - @click="handleUpdateProject(scope.row)"></el-button> + <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Edit" @click="handleUpdateProject(scope.row)"></el-button> </el-tooltip> <el-tooltip v-if="scope.row.userId !== 1" content="上传证书目录" placement="top"> - <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Upload" - @click="handleUploadCert(scope.row)"></el-button> + <el-button v-hasPermi="['system:user:edit']" link type="primary" icon="Upload" @click="handleUploadCert(scope.row)"></el-button> </el-tooltip> </template> </el-table-column> @@ -148,117 +142,53 @@ <shuttle-frame :userId="selectedUserId" @close="shuttleVisible = false" /> </el-dialog> - <pagination v-show="total > 0" v-model:page="queryParams.pageNum" v-model:limit="queryParams.pageSize" - :total="total" @pagination="getList" /> + <pagination + v-show="total > 0" + v-model:page="queryParams.pageNum" + v-model:limit="queryParams.pageSize" + :total="total" + @pagination="getList" + /> </el-card> </el-col> </el-row> <!-- 添加或修改用户配置对话框 --> - <el-dialog ref="formDialogRef" v-model="dialog.visible" :title="dialog.title" width="600px" append-to-body - @close="closeDialog"> - <el-form ref="userFormRef" :model="form" :rules="rules" label-width="80px"> - <el-row> - <el-col :span="12"> - <el-form-item label="用户昵称" prop="nickName"> - <el-input v-model="form.nickName" placeholder="请输入用户昵称" maxlength="30" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="归属部门" prop="deptId"> - <el-tree-select v-model="form.deptId" :data="enabledDeptOptions" - :props="{ value: 'id', label: 'label', children: 'children' }" value-key="id" placeholder="请选择归属部门" - check-strictly @change="handleDeptChange" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="手机号码" prop="phonenumber"> - <el-input v-model="form.phonenumber" placeholder="请输入手机号码" maxlength="11" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="邮箱" prop="email"> - <el-input v-model="form.email" placeholder="请输入邮箱" maxlength="50" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item v-if="form.userId == undefined" label="用户名称" prop="userName"> - <el-input v-model="form.userName" placeholder="请输入用户名称" maxlength="30" /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item v-if="form.userId == undefined" label="用户密码" prop="password"> - <el-input v-model="form.password" placeholder="请输入用户密码" type="password" maxlength="20" show-password /> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="用户性别"> - <el-select v-model="form.sex" placeholder="请选择"> - <el-option v-for="dict in sys_user_sex" :key="dict.value" :label="dict.label" - :value="dict.value"></el-option> - </el-select> - </el-form-item> - </el-col> - <el-col :span="12"> - <el-form-item label="岗位"> - <el-select v-model="form.postIds" multiple placeholder="请选择"> - <el-option v-for="item in postOptions" :key="item.postId" :label="item.postName" :value="item.postId" - :disabled="item.status == '1'"></el-option> - </el-select> - </el-form-item> - </el-col> - <el-col :span="24"> - <el-row :gutter="20" v-for="(item, index) in form.projectRoles"> - <el-col :span="11" :offset="0"> - <el-form-item label="项目列表"> - <el-select v-model="item.projectId" placeholder="请选择"> - <el-option v-for="dict in projectOptions" :key="dict.id" :label="dict.shortName" - :value="dict.id"></el-option> - </el-select> - </el-form-item> - </el-col> - <el-col :span="11" :offset="0"> - <el-form-item label="角色"> - <el-select v-model="item.roleIds" filterable multiple placeholder="请选择"> - <el-option v-for="item in roleOptions" :key="item.roleId" :label="item.roleName" - :value="item.roleId" :disabled="item.status == '1'"></el-option> - </el-select> - </el-form-item> - </el-col> - <el-col :span="1" :offset="0"> - <el-button type="primary" circle icon="Plus" @click="handleAddProject" v-if="index == 0"></el-button> - <el-button type="danger" circle icon="Delete" @click="delProject(index)" v-else></el-button> - </el-col> - </el-row> - </el-col> - <el-col :span="12"> - <el-form-item label="状态"> - <el-radio-group v-model="form.status"> - <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ dict.label }} - </el-radio> - </el-radio-group> - </el-form-item> - </el-col> - <el-col :span="24"> - <el-form-item label="备注"> - <el-input v-model="form.remark" type="textarea" placeholder="请输入内容"></el-input> - </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> + <el-dialog draggable ref="formDialogRef" style="background: rgb(249,250,251);" v-model="dialog.visible" :title="dialog.title" width="1300px" append-to-body @close="closeDialog"> + <div class="boxDetial"> + <div class="tab_info"> + <div class="tab_item" @click="onTab(1)" :class="{ active: type == 1 }"> + <Avatar style="width: 1em; height: 1em; margin-right: 8px" :style="{color: type == 1 ? '#1890ff' : '#000'}" /> + <span>基本资料</span> + </div> + <div class="tab_item" @click="onTab(2)" :class="{ active: type == 2 }"> + <Key style="width: 1em; height: 1em; margin-right: 8px" :style="{color: type == 2 ? '#1890ff' : '#000'}" /> + <span>角色信息</span> + </div> </div> - </template> + <div class="tab_content" v-show="type == 1"> + <editInfo ref="editInfoRef" @close="dialog.visible = false" @submit="getList" @setDeptId="setDeptId"></editInfo> + </div> + <div class="tab_content" v-show="type == 2"> + <roleInfo ref="roleInfoRef" @close="dialog.visible = false" @submit="getList"></roleInfo> + </div> + </div> </el-dialog> <!-- 用户导入对话框 --> <el-dialog draggable v-model="upload.open" :title="upload.title" width="400px" append-to-body> - <el-upload ref="uploadRef" :limit="1" accept=".xlsx, .xls" :headers="upload.headers" - :action="upload.url + '?updateSupport=' + upload.updateSupport" :disabled="upload.isUploading" - :on-progress="handleFileUploadProgress" :on-success="handleFileSuccess" :auto-upload="false" drag> + <el-upload + ref="uploadRef" + :limit="1" + accept=".xlsx, .xls" + :headers="upload.headers" + :action="upload.url + '?updateSupport=' + upload.updateSupport" + :disabled="upload.isUploading" + :on-progress="handleFileUploadProgress" + :on-success="handleFileSuccess" + :auto-upload="false" + drag + > <el-icon class="el-icon--upload"> <i-ep-upload-filled /> </el-icon> @@ -270,8 +200,7 @@ 是否更新已经存在的用户数据 </div> <span>仅允许导入xls、xlsx格式文件。</span> - <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" - @click="importTemplate">下载模板 </el-link> + <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">下载模板 </el-link> </div> </template> </el-upload> @@ -307,7 +236,9 @@ import { to } from 'await-to-js'; import { getProjectByDeptId, getRoleList, optionselect } from '@/api/system/post'; import ShuttleFrame from '../../project/projectRelevancy/component/ShuttleFrame.vue'; import { listProject } from '@/api/project/project'; - +import editInfo from './comm/editInfo.vue'; +import roleInfo from './comm/roleInfo.vue'; +import { color } from 'echarts'; const router = useRouter(); const { proxy } = getCurrentInstance() as ComponentInternalInstance; const { sys_normal_disable, sys_user_sex } = toRefs<any>(proxy?.useDict('sys_normal_disable', 'sys_user_sex')); @@ -326,7 +257,8 @@ const initPassword = ref<string>(''); const postOptions = ref<PostVO[]>([]); const roleOptions = ref<RoleVO[]>([]); const projectOptions = ref<any[]>([]); - +const editInfoRef = ref<InstanceType<typeof editInfo> | null>(null); +const roleInfoRef = ref<InstanceType<typeof roleInfo> | null>(null); /*** 用户导入参数 */ const upload = reactive<ImportOption>({ // 是否显示弹出层(用户导入) @@ -358,7 +290,8 @@ const queryFormRef = ref<ElFormInstance>(); const userFormRef = ref<ElFormInstance>(); const uploadRef = ref<ElUploadInstance>(); const formDialogRef = ref<ElDialogInstance>(); - +const deptIdRole = ref<number>(); +const type = ref(1); const dialog = reactive<DialogOption>({ visible: false, title: '' @@ -452,7 +385,9 @@ watchEffect( flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行 } ); - +const setDeptId = (deptId: number) => { + deptIdRole.value = deptId; +}; /** 查询用户列表 */ const getList = async () => { loading.value = true; @@ -637,8 +572,12 @@ const cancel = () => { const handleAdd = async () => { reset(); const { data } = await api.getUser(); + type.value = 1; dialog.visible = true; dialog.title = '新增用户'; + nextTick(() => { + editInfoRef.value?.open(); + }); postOptions.value = data.posts; form.value.password = initPassword.value.toString(); }; @@ -646,19 +585,13 @@ const handleAdd = async () => { /** 修改按钮操作 */ const handleUpdate = async (row?: UserForm) => { reset(); - const userId = row?.userId || ids.value[0]; - const { data } = await api.getUser(userId); dialog.visible = true; dialog.title = '修改用户'; - Object.assign(form.value, data.user); - postOptions.value = data.posts; - roleOptions.value = data.roles; - form.value.postIds = data.postIds; - form.value.projectRoles = data.projectRoles; - form.value.password = ''; - const roleList = await getRoleList(form.value.deptId); - - roleOptions.value = roleList.data; + type.value = 1; + form.value = row; + nextTick(() => { + editInfoRef.value?.open(row); + }); }; const validate = () => { @@ -764,6 +697,58 @@ const uploadCert = async () => { certDialog.value = false; proxy?.$modal.msgSuccess('上传证书目录成功'); }; +const onTab = (val: number) => { + type.value = val; + if (val == 2) { + let obj = editInfoRef.value?.getInfoForm(); + form.value = obj; + nextTick(() => { + roleInfoRef.value?.open(form.value, deptIdRole.value); + }); + } +}; </script> -<style lang="scss" scoped></style> +<style lang="scss" scoped> +.boxDetial { + display: flex; + justify-content: space-between; + align-items: start; + height: 680px; + gap: 20px; + + .tab_info { + height: 40%; + width: 200px; + background: #fff; + padding: 30px 10px; + text-align: center; + display: flex; + flex-direction: column; + border-radius: 0.5rem; + font-size: 18px; + .tab_item { + display: flex; + align-items: center; + justify-content: center; + gap: 5px; + margin-bottom: 10px; + padding: 5px 0; + cursor: pointer; + } + .tab_item:hover { + background-color: rgb(249, 250, 251); + } + .active { + color: #1890ff; + } + } + .tab_content { + height: 100%; + width: calc(100% - 200px); + padding: 20px 10px; + background: #fff; + border-radius: 0.5rem; + } +} +</style> diff --git a/src/views/tender/bidd/index.vue b/src/views/tender/bidd/index.vue index 3c3972b..1bf3b61 100644 --- a/src/views/tender/bidd/index.vue +++ b/src/views/tender/bidd/index.vue @@ -10,7 +10,7 @@ <el-option v-for="item in options" :key="item.versions" :label="item.versions" :value="item.versions" /> </el-select> </el-form-item> - <el-form-item label="表名" prop="sheet"> + <el-form-item label="表名" prop="sheet" v-if="activeTab != '3'"> <el-select v-model="queryForm.sheet" placeholder="选择表名" @change="changeSheet"> <el-option v-for="item in sheets" :key="item" :label="item" :value="item" /> </el-select> @@ -58,11 +58,13 @@ </el-card> </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 ref="tableAllRef" v-loading="loading" :data="tableData" row-key="id" border lazy :expand-row-keys="expandRowKeys"> <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="specification" label="规格" align="center" /> + <el-table-column prop="quantity" label="数量" align="center"> <template #default="scope"> {{ scope.row.children.length > 0 ? '' : scope.row.quantity }} </template> @@ -90,6 +92,11 @@ {{ scope.row.price != 0 ? Number(scope.row.price).toFixed(2) : null }} </template> </el-table-column> + <el-table-column prop="taxRate" label="税率" width="100"> + <template #default="scope"> + {{ scope.row.taxRate !== false ? scope.row.taxRate : '' }} + </template> + </el-table-column> <el-table-column prop="operate" label="操作" align="center"> <template #default="scope"> <el-button @@ -111,64 +118,141 @@ </template> <script setup lang="ts"> +import { ref, computed, watch, onMounted, onUnmounted } from 'vue'; +import { getCurrentInstance, ComponentInternalInstance } from 'vue'; +import { ElMessage } from 'element-plus'; import { useUserStoreHook } from '@/store/modules/user'; import { obtainAllVersionNumbers, sheetList, getTableList, updatePrice, importExcelFile } from '@/api/tender/index'; +// 类型定义 +interface QueryForm { + versions: string; + sheet: string; +} + +interface TableRow { + id: string | number; + num?: string; + name?: string; + unit?: string; + taxRate?: string | number; + specification?: string; + quantity?: number; + unitPrice?: number; + price?: number; + remark?: string; + children?: TableRow[]; +} + +interface VersionItem { + versions: string; + status: string; + id?: string | number; + [key: string]: any; +} + +// 实例与状态初始化 const { proxy } = getCurrentInstance() as ComponentInternalInstance; const userStore = useUserStoreHook(); const currentProject = computed(() => userStore.selectedProject); + +// 标签页配置 const tabList = [ - { - label: '招采工程量清单', - value: '2' - }, - { - label: '物资设备清单', - value: '3' - } + { label: '招采工程量清单', value: '2' }, + { label: '物资设备清单', value: '3' } ]; -const queryForm = ref({ - versions: '', - sheet: '' -}); - -const versionsData = ref<any>({}); +// 响应式状态 +const queryForm = ref<QueryForm>({ versions: '', sheet: '' }); +const versionsData = ref<VersionItem>({}); const activeTab = ref('2'); -const sheets = ref([]); -const options = ref([]); -const tableData = ref([]); -const tableRef = ref(); -const isExpandAll = ref(false); +const sheets = ref<string[]>([]); +const options = ref<VersionItem[]>([]); +const tableData = ref<TableRow[]>([]); +const tableAllRef = ref<any>(null); +const uploadRef = ref<any>(null); +const isExpandAll = ref(true); const loading = ref(false); -const versionMap = new Map(); +const versionMap = new Map<string, VersionItem>(); +const expandRowKeys = ref<string[] | number[]>([]); // 控制表格展开的行ID +const modifyPrice = new Map<string | number, TableRow>(); -// 切换tab +// 切换标签页 const handleTabChange = (tab: string) => { activeTab.value = tab; tableData.value = []; versionsData.value = {}; + isExpandAll.value = true; + expandRowKeys.value = []; getVersionNums(); }; -//切换版本 -const changeVersions = (value) => { - versionsData.value = options.value.find((item) => item.versions == value); + +// 切换版本号 +const changeVersions = (value: string) => { + versionsData.value = options.value.find((item) => item.versions === value) || {}; getSheetName(); }; -//切换表格 -const changeSheet = (val: any) => { + +// 切换表名 +const changeSheet = (val: string) => { getTableData(); }; -//展开树 -const toggleExpandAll = () => { + +// 一键展开/收起 +const toggleExpandAll = async () => { isExpandAll.value = !isExpandAll.value; - console.log(isExpandAll.value); - tableData.value.forEach((row) => { - tableRef.value.toggleRowExpansion(row, isExpandAll.value); + if (!tableData.value.length || !tableAllRef.value) return; + + if (isExpandAll.value) { + // 收集所有已加载节点的ID + const allExpandIds = collectAllNodeIds(tableData.value); + expandRowKeys.value = allExpandIds; + + // 处理懒加载节点 + await loadAndExpandAllLazyNodes(); + } else { + // 全部收起 + expandRowKeys.value = []; + } +}; +// 辅助函数:递归收集所有节点ID(包括子节点) +const collectAllNodeIds = (nodes: TableRow[]): (string | number)[] => { + let ids: (string | number)[] = []; + nodes.forEach((node) => { + ids.push(node.id); + if (node.children && node.children.length > 0) { + ids = [...ids, ...collectAllNodeIds(node.children)]; + } }); + return ids; }; -//获取版本号 +// 辅助函数:加载并展开所有懒加载子节点 +const loadAndExpandAllLazyNodes = async () => { + if (!tableAllRef.value) return; + try { + // 获取所有可能有子节点的父节点 + const parentNodes = tableData.value.filter((node) => node.hasChildren || (node.children && node.children.length === 0)); + if (!parentNodes.length) return; + + // 逐个加载并展开子节点 + for (const parent of parentNodes) { + if (tableAllRef.value.loadOrToggleRow) { + await tableAllRef.value.loadOrToggleRow(parent); + } + await nextTick(); + // 递归展开子节点的子节点 + if (parent.children && parent.children.length > 0) { + const childIds = collectAllNodeIds(parent.children); + expandRowKeys.value = [...new Set([...expandRowKeys.value, ...childIds])]; + } + } + } catch (error) { + console.error('加载并展开懒加载节点失败:', error); + } finally { + } +}; +// 获取版本号列表 const getVersionNums = async () => { try { const params = { @@ -179,93 +263,126 @@ const getVersionNums = async () => { }; const res = await obtainAllVersionNumbers(params); - if (res.code == 200) { + if (res.code === 200) { options.value = res.data; - if (res.data.length > 0) { - res.data.forEach((item: any) => { - versionMap.set(item.versions, item); - }); - queryForm.value.versions = res.data[0].versions; - versionsData.value = options.value.find((item) => item.versions == queryForm.value.versions); + versionMap.clear(); + options.value.forEach((item) => versionMap.set(item.versions, item)); + + if (options.value.length > 0) { + queryForm.value.versions = options.value[0].versions; + versionsData.value = options.value[0]; getSheetName(); } else { queryForm.value.versions = ''; - // getSheetName(); + sheets.value = []; + tableData.value = []; + expandRowKeys.value = []; } } } catch (error) { - console.log(error); + console.error('获取版本号失败:', error); + ElMessage.error('获取版本号失败,请刷新重试'); } }; -//获取表名 + +// 获取表名列表 const getSheetName = async () => { try { const params = { projectId: currentProject.value?.id, versions: queryForm.value.versions }; + const res = await sheetList(params); - if (res.code == 200) { + if (res.code === 200) { sheets.value = res.data; - if (res.data.length > 0) { - queryForm.value.sheet = res.data[0]; + if (sheets.value.length > 0) { + queryForm.value.sheet = sheets.value[0]; } else { queryForm.value.sheet = ''; + tableData.value = []; + expandRowKeys.value = []; } getTableData(); } } catch (error) { - console.log(error); + console.error('获取表名失败:', error); + ElMessage.error('获取表名失败,请刷新重试'); } }; -//获取表格数据 + +// 获取表格数据 const getTableData = async () => { try { loading.value = true; - const params = { projectId: currentProject.value?.id, versions: queryForm.value.versions, sheet: queryForm.value.sheet, type: activeTab.value }; + const res = await getTableList(params); - if (res.code == 200) { + if (res.code === 200) { tableData.value = res.data; + if (isExpandAll.value) { + // 展开全部 + isExpandAll.value = false; + toggleExpandAll(); + } + modifyPrice.clear(); } } catch (error) { - console.log(error); + console.error('获取表格数据失败:', error); + ElMessage.error('获取表格数据失败,请刷新重试'); } finally { loading.value = false; } }; -//导入 -const importExcel = (options: any): any => { - let formData = new FormData(); + +// 导入Excel +const importExcel = (options: any): void => { + if (!queryForm.value.versions || (activeTab.value !== '3' && !queryForm.value.sheet)) { + ElMessage.warning('请先选择版本号和表名(物资设备清单无需选择表名)'); + return; + } + + const formData = new FormData(); formData.append('file', options.file); loading.value = true; + importExcelFile( - { projectId: currentProject.value?.id, sheet: queryForm.value.sheet, versions: queryForm.value.versions, type: activeTab.value }, + { + projectId: currentProject.value?.id, + sheet: queryForm.value.sheet, + versions: queryForm.value.versions, + type: activeTab.value + }, formData ) .then((res) => { - const { code } = res; - if (code == 200) { - proxy.$modal.msgSuccess(res.msg || '导入成功'); + if (res.code === 200) { + proxy?.$modal.msgSuccess(res.msg || '导入成功'); getTableData(); } else { - proxy.$modal.msgError(res.msg || '导入失败'); + proxy?.$modal.msgError(res.msg || '导入失败'); } }) .catch((err) => { - proxy.$modal.msgError(err.msg || '导入失败'); + proxy?.$modal.msgError(err.msg || '导入失败'); }) .finally(() => { loading.value = false; }); }; -//导出 + +// 导出Excel const handleExport = () => { + if (!queryForm.value.versions || (activeTab.value !== '3' && !queryForm.value.sheet)) { + ElMessage.warning('请先选择版本号和表名(物资设备清单无需选择表名)'); + return; + } + proxy?.download( '/tender/tenderPlanLimitList/export', { @@ -274,90 +391,108 @@ const handleExport = () => { versions: queryForm.value.versions, type: activeTab.value }, - `招标一览表${queryForm.value.sheet}.xlsx` + `招标一览_${queryForm.value.sheet || (activeTab.value === '3' ? '物资设备清单' : '')}_v${queryForm.value.versions}.xlsx` ); }; -const modifyPrice = new Map(); -const changePrice = (row: any) => { - modifyPrice.set(row.id, row); - // if (!row.unitPrice) { - // modifyPrice.delete(row.id); - // } -}; -//修改单价 -const handleSave = (row?: any, type?: any) => { - try { - if (type == 'single') { - loading.value = true; - const list = [{ ...row, type: activeTab.value }]; - updatePrice(list).then((res) => { - if (res.code == 200) { - ElMessage({ - message: '修改成功', - type: 'success' - }); - getTableData(); - } - }); - } - if (type == 'all') { - loading.value = true; - const list = []; - modifyPrice.forEach((item) => { - list.push({ ...item, type: activeTab.value }); - }); - updatePrice(list).then((res) => { - if (res.code == 200) { - ElMessage({ - message: '修改成功', - type: 'success' - }); - getTableData(); - } - }); - } - } catch (error) { - ElMessage({ - message: '修改失败', - type: 'error' - }); - } finally { - loading.value = false; +// 记录待修改价格的行 +const changePrice = (row: TableRow) => { + if (row.id && row.unitPrice !== undefined) { + modifyPrice.set(row.id, row); + } else if (row.id) { + modifyPrice.delete(row.id); } }; -/** 审核按钮操作 */ -const handleAudit = async () => { - let id = versionMap.get(queryForm.value.versions).id; - console.log(id); - if (activeTab.value == '2') { +// 保存价格修改 +const handleSave = (row?: TableRow, type?: 'single' | 'all') => { + if (versionsData.value.status !== 'draft') { + ElMessage.warning('仅草稿状态可修改单价'); + return; + } + + let updateList: TableRow[] = []; + if (type === 'single' && row?.id) { + updateList = [{ ...row, type: activeTab.value }]; + } else if (type === 'all') { + updateList = Array.from(modifyPrice.values()).map((item) => ({ ...item, type: activeTab.value })); + if (updateList.length === 0) { + ElMessage.warning('暂无待修改的单价数据'); + return; + } + } + + loading.value = true; + updatePrice(updateList) + .then((res) => { + if (res.code === 200) { + ElMessage.success('修改成功'); + getTableData(); + modifyPrice.clear(); + } else { + ElMessage.error(res.msg || '修改失败'); + } + }) + .catch((err) => { + ElMessage.error(err.msg || '修改失败'); + }) + .finally(() => { + loading.value = false; + }); +}; + +// 审核/查看流程 +const handleAudit = () => { + const versionItem = versionMap.get(queryForm.value.versions); + if (!versionItem?.id) { + ElMessage.warning('请先选择有效的版本号'); + return; + } + + if (activeTab.value === '2') { proxy?.$tab.openPage('/approval/tenderBidd/indexEdit', '招采工程量清单审核', { - id: id, + id: versionItem.id, type: 'update' }); - } - if (activeTab.value == '3') { + } else if (activeTab.value === '3') { proxy?.$tab.openPage('/approval/tenderBidd/indexEdit2', '物资设备清单审核', { - id: id, + id: versionItem.id, type: 'update' }); } }; -//监听项目id刷新数据 +// 监听项目切换 const listeningProject = watch( () => currentProject.value?.id, - (nid, oid) => { - getVersionNums(); + (newId, oldId) => { + if (newId && newId !== oldId) { + getVersionNums(); + } else { + tableData.value = []; + options.value = []; + sheets.value = []; + queryForm.value = { versions: '', sheet: '' }; + versionsData.value = {}; + expandRowKeys.value = []; + versionMap.clear(); + modifyPrice.clear(); + } } ); -onUnmounted(() => { - listeningProject(); -}); + +// 生命周期钩子 onMounted(() => { getVersionNums(); }); + +onUnmounted(() => { + listeningProject(); +}); </script> -<style scoped lang="scss"></style> +<style scoped lang="scss"> +.mb8 { + margin-bottom: 8px; +} +</style> diff --git a/src/views/tender/plan/index.vue b/src/views/tender/plan/index.vue index 7c69723..ba71c3a 100644 --- a/src/views/tender/plan/index.vue +++ b/src/views/tender/plan/index.vue @@ -191,9 +191,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.quantity ? Number(scope.row.quantity) : 0) - + (scope.row.useQuantity ? Number(scope.row.useQuantity) : 0) - + (scope.row.selectNum ? Number(scope.row.selectNum) : 0) == + 0 + ? activeTab == 2 + ? 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) }} </template> </el-table-column> @@ -219,12 +226,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) }} @@ -459,19 +470,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) => { @@ -612,17 +615,21 @@ const changeBiddingTime = (value: any, row: any) => { }; //修改合同金额 const changeContractPrice = (value: any, row: any) => { - updateTenderPlan({ - ...row - }).then((res) => { - if (res.code == 200) { - ElMessage({ - message: '修改成功', - type: 'success' - }); - getList(); - } - }); + if (value <= Number(row.price)) { + updateTenderPlan({ + ...row + }).then((res) => { + if (res.code == 200) { + ElMessage({ + message: '修改成功', + type: 'success' + }); + getList(); + } + }); + } else { + ElMessage.error('合同金额不能大于限价金额'); + } }; //上传投标文件 diff --git a/src/views/tender/supplierInput/index.vue b/src/views/tender/supplierInput/index.vue index cea16ef..e94b3ab 100644 --- a/src/views/tender/supplierInput/index.vue +++ b/src/views/tender/supplierInput/index.vue @@ -234,17 +234,17 @@ <!-- 第十一行:注册人员数量(仅劳务类型显示) --> <el-row :gutter="20" class="mb-4" v-if="form.supplierType === '劳务'"> <el-col :span="12"> - <el-form-item label="一建建造师" prop="build1"> - <el-input v-model="form.build1" placeholder="请输入一建建造师数量" clearable /> + <el-form-item label="一建建造师" prop="firstBuildingNumber"> + <el-input v-model="form.firstBuildingNumber" placeholder="请输入一建建造师数量" clearable /> </el-form-item> - <el-form-item label="二建建造师" prop="build2"> - <el-input v-model="form.build2" placeholder="请输入二建建造师数量" clearable /> + <el-form-item label="二建建造师" prop="secondBuildingNumber"> + <el-input v-model="form.secondBuildingNumber" placeholder="请输入二建建造师数量" clearable /> </el-form-item> - <el-form-item label="注册造价工程师" prop="build3"> - <el-input v-model="form.build3" placeholder="请输入注册造价工程师数量" clearable /> + <el-form-item label="注册造价工程师" prop="registeredEngineerNumber"> + <el-input v-model="form.registeredEngineerNumber" placeholder="请输入注册造价工程师数量" clearable /> </el-form-item> - <el-form-item label="其他(分别写)" prop="build4"> - <el-input v-model="form.build4" placeholder="请输入其他人员数量" clearable /> + <el-form-item label="其他(分别写)" prop="otherBuildingNumber"> + <el-input v-model="form.otherBuildingNumber" placeholder="请输入其他人员数量" clearable /> </el-form-item> </el-col> <el-col :span="12"> @@ -254,17 +254,17 @@ <!-- 第十二行:职称人员数量(仅劳务类型显示) --> <el-row :gutter="20" class="mb-4" v-if="form.supplierType === '劳务'"> <el-col :span="12"> - <el-form-item label="高级工程师人数" prop="personnelNumber1"> - <el-input v-model="form.personnelNumber1" placeholder="请输高级工程师数量" clearable /> + <el-form-item label="高级工程师人数" prop="seniorEngineerNumber"> + <el-input v-model="form.seniorEngineerNumber" placeholder="请输高级工程师数量" clearable /> </el-form-item> - <el-form-item label="工程师数量" prop="personnelNumber2"> - <el-input v-model="form.personnelNumber2" placeholder="请输入工程师数量" clearable /> + <el-form-item label="工程师数量" prop="engineerNumber"> + <el-input v-model="form.engineerNumber" placeholder="请输入工程师数量" clearable /> </el-form-item> - <el-form-item label="助理工程师数量" prop="personnelNumber3"> - <el-input v-model="form.personnelNumber3" placeholder="请输入助理工程师数量" clearable /> + <el-form-item label="助理工程师数量" prop="assistantEngineerNumber"> + <el-input v-model="form.assistantEngineerNumber" placeholder="请输入助理工程师数量" clearable /> </el-form-item> - <el-form-item label="其他人员数量" prop="personnelNumber4"> - <el-input v-model="form.personnelNumber4" placeholder="请输入其他人员数量" clearable /> + <el-form-item label="其他人员数量" prop="otherPersonnelNumber"> + <el-input v-model="form.otherPersonnelNumber" placeholder="请输入其他人员数量" clearable /> </el-form-item> </el-col> <el-col :span="12"> @@ -285,8 +285,8 @@ :data="form" uploadUrl="/supplierInput/supplierInput" :limit="1" - :onUploadSuccess="handleUploadSuccess" - @handleChange="change" + @handleChange="handleFileChange" + @handleRemove="handleFileRemove" showFileList > <div><el-button type="primary">上传文件</el-button><br /></div> @@ -309,7 +309,7 @@ <script setup name="SupplierInput" lang="ts"> import { ComponentInternalInstance, getCurrentInstance, onMounted, ref, reactive, toRefs, computed } from 'vue'; import { ElFormInstance } from 'element-plus'; -import { listSupplierInput, getSupplierInput, delSupplierInput } from '@/api/supplierInput/supplierInput/index'; +import { listSupplierInput, getSupplierInput, delSupplierInput, updateSupplierInput } from '@/api/supplierInput/supplierInput/index'; import { SupplierInputVO, SupplierInputQuery, SupplierInputForm, PageData, DialogOption } from '@/api/supplierInput/supplierInput/types'; import Pagination from '@/components/Pagination/index.vue'; import RightToolbar from '@/components/RightToolbar/index.vue'; @@ -369,14 +369,14 @@ const initFormData: any = { inputFile: undefined, // state: '0', // 新增默认待审核 // 新增:用于表单输入的单独字段 - build1: undefined, // 一建建造师 - build2: undefined, // 二建建造师 - build3: undefined, // 注册造价工程师 - build4: undefined, // 其他注册人员 - personnelNumber1: undefined, // 高级工程师 - personnelNumber2: undefined, // 工程师 - personnelNumber3: undefined, // 助理工程师 - personnelNumber4: undefined // 其他职称人员 + firstBuildingNumber: undefined, // 一建建造师 + secondBuildingNumber: undefined, // 二建建造师 + registeredEngineerNumber: undefined, // 注册造价工程师 + otherBuildingNumber: undefined, // 其他注册人员 + seniorEngineerNumber: undefined, // 高级工程师 + engineerNumber: undefined, // 工程师 + assistantEngineerNumber: undefined, // 助理工程师 + otherPersonnelNumber: undefined // 其他职称人员 }; // 核心数据(表单+查询参数) const data = reactive<PageData<SupplierInputForm, SupplierInputQuery>>({ @@ -411,14 +411,14 @@ const rules = computed(() => { { pattern: /^1[3-9]\d{9}$/, message: '请输入正确的手机号格式', trigger: 'blur' } ], id: [{ required: true, message: 'ID不能为空', trigger: 'blur' }], - build1: [{ required: true, message: '请输入一建建造师数量', trigger: 'change' }], - build2: [{ required: true, message: '请输入二建建造师数量', trigger: 'change' }], - build3: [{ required: true, message: '请输入注册造价工程师数量', trigger: 'change' }], - build4: [{ required: true, message: '请输入其他数量', trigger: 'change' }], - personnelNumber1: [{ required: true, message: '请输入高级工程师数量', trigger: 'change' }], - personnelNumber2: [{ required: true, message: '请输入工程师数量', trigger: 'change' }], - personnelNumber3: [{ required: true, message: '请输入助理工程师数量', trigger: 'change' }], - personnelNumber4: [{ required: true, message: '请输入其他数量', trigger: 'change' }] + firstBuildingNumber: [{ required: true, message: '请输入一建建造师数量', trigger: 'change' }], + secondBuildingNumber: [{ required: true, message: '请输入二建建造师数量', trigger: 'change' }], + registeredEngineerNumber: [{ required: true, message: '请输入注册造价工程师数量', trigger: 'change' }], + otherBuildingNumber: [{ required: true, message: '请输入其他数量', trigger: 'change' }], + seniorEngineerNumber: [{ required: true, message: '请输入高级工程师数量', trigger: 'change' }], + engineerNumber: [{ required: true, message: '请输入工程师数量', trigger: 'change' }], + assistantEngineerNumber: [{ required: true, message: '请输入助理工程师数量', trigger: 'change' }], + otherPersonnelNumber: [{ required: true, message: '请输入其他数量', trigger: 'change' }] }; // 仅当类型为"劳务"时,添加安全生产许可证+人员数量校验 @@ -430,15 +430,15 @@ const rules = computed(() => { safeCodeData: [{ required: true, message: '请选择安全生产许可证发证日期', trigger: 'change' }], safeCertificateValidity: [{ required: true, message: '请选择安全生产许可证有效期', trigger: 'change' }], // 注册人员数量校验 - build1: [{ required: true, message: '请输入一建建造师数量', trigger: 'blur' }], - build2: [{ required: true, message: '请输入二建建造师数量', trigger: 'blur' }], - build3: [{ required: true, message: '请输入注册造价工程师数量', trigger: 'blur' }], - build4: [{ required: true, message: '请输入其他注册人员数量', trigger: 'blur' }], + firstBuildingNumber: [{ required: true, message: '请输入一建建造师数量', trigger: 'blur' }], + secondBuildingNumber: [{ required: true, message: '请输入二建建造师数量', trigger: 'blur' }], + registeredEngineerNumber: [{ required: true, message: '请输入注册造价工程师数量', trigger: 'blur' }], + otherBuildingNumber: [{ required: true, message: '请输入其他注册人员数量', trigger: 'blur' }], // 职称人员数量校验 - personnelNumber1: [{ required: true, message: '请输入高级工程师数量', trigger: 'blur' }], - personnelNumber2: [{ required: true, message: '请输入工程师数量', trigger: 'blur' }], - personnelNumber3: [{ required: true, message: '请输入助理工程师数量', trigger: 'blur' }], - personnelNumber4: [{ required: true, message: '请输入其他职称人员数量', trigger: 'blur' }] + seniorEngineerNumber: [{ required: true, message: '请输入高级工程师数量', trigger: 'blur' }], + engineerNumber: [{ required: true, message: '请输入工程师数量', trigger: 'blur' }], + assistantEngineerNumber: [{ required: true, message: '请输入助理工程师数量', trigger: 'blur' }], + otherPersonnelNumber: [{ required: true, message: '请输入其他职称人员数量', trigger: 'blur' }] }; } @@ -455,22 +455,26 @@ const handleTypeChange = () => { form.value.registeredNumber = undefined; form.value.personnelNumber = undefined; // 清空表单单独字段 - form.value.build1 = form.value.build2 = form.value.build3 = form.value.build4 = undefined; - form.value.personnelNumber1 = form.value.personnelNumber2 = form.value.personnelNumber3 = form.value.personnelNumber4 = undefined; + form.value.firstBuildingNumber = + form.value.secondBuildingNumber = + form.value.registeredEngineerNumber = + form.value.otherBuildingNumber = + undefined; + form.value.seniorEngineerNumber = form.value.engineerNumber = form.value.assistantEngineerNumber = form.value.otherPersonnelNumber = undefined; } // 重置隐藏字段的校验状态,避免错误提示残留 supplierInputFormRef.value?.clearValidate([ 'safeCode', 'safeCodeData', 'safeCertificateValidity', - 'build1', - 'build2', - 'build3', - 'build4', - 'personnelNumber1', - 'personnelNumber2', - 'personnelNumber3', - 'personnelNumber4' + 'firstBuildingNumber', + 'secondBuildingNumber', + 'registeredEngineerNumber', + 'otherBuildingNumber', + 'seniorEngineerNumber', + 'engineerNumber', + 'assistantEngineerNumber', + 'otherPersonnelNumber' ]); }; @@ -517,18 +521,18 @@ const resetQuery = () => { const splitBackEndStrToForm = (resData: any) => { if (resData.registeredNumber) { const registeredArr = resData.registeredNumber.split(','); - form.value.build1 = registeredArr[0] || undefined; // 一建建造师 - form.value.build2 = registeredArr[1] || undefined; // 二建建造师 - form.value.build3 = registeredArr[2] || undefined; // 注册造价工程师 - form.value.build4 = registeredArr[3] || undefined; // 其他注册人员 + form.value.firstBuildingNumber = registeredArr[0] || undefined; // 一建建造师 + form.value.secondBuildingNumber = registeredArr[1] || undefined; // 二建建造师 + form.value.registeredEngineerNumber = registeredArr[2] || undefined; // 注册造价工程师 + form.value.otherBuildingNumber = registeredArr[3] || undefined; // 其他注册人员 } if (resData.personnelNumber) { const personnelArr = resData.personnelNumber.split(','); - form.value.personnelNumber1 = personnelArr[0] || undefined; // 高级工程师 - form.value.personnelNumber2 = personnelArr[1] || undefined; // 工程师 - form.value.personnelNumber3 = personnelArr[2] || undefined; // 助理工程师 - form.value.personnelNumber4 = personnelArr[3] || undefined; // 其他职称人员 + form.value.seniorEngineerNumber = personnelArr[0] || undefined; // 高级工程师 + form.value.engineerNumber = personnelArr[1] || undefined; // 工程师 + form.value.assistantEngineerNumber = personnelArr[2] || undefined; // 助理工程师 + form.value.otherPersonnelNumber = personnelArr[3] || undefined; // 其他职称人员 } }; /** 审核过程按钮操作 */ @@ -572,6 +576,7 @@ const handleAdd = () => { dialog.title = '添加供应商入库'; }; +const editFileId = ref(''); const handleUpdate = async (row?: SupplierInputVO) => { reset(); const _id = row?.id || ids.value[0]; @@ -579,7 +584,8 @@ const handleUpdate = async (row?: SupplierInputVO) => { try { const res = await getSupplierInput(_id); - const resData = res.data || {}; + const resData: any = res.data || {}; + editFileId.value = resData.fileId; // 1. 基础字段回显 form.value = { ...form.value, ...resData, inputFile: '' }; // 2. 核心修复:拆分后端拼接字符串到表单单独字段 @@ -594,32 +600,61 @@ const handleUpdate = async (row?: SupplierInputVO) => { } }; +const fileStatus = ref(false); +const updateFileStatus = ref(true); +const isUpdateFile = ref(false); //记录是否在修改页面时是否有新上传的文件 +const handleFileChange = (file, fileList) => { + if (form.value.id) { + updateFileStatus.value = true; + isUpdateFile.value = true; //记录是否在修改页面时是否有新上传的文件 + } + fileStatus.value = true; +}; + +const handleFileRemove = (file, fileList) => { + if (form.value.id) { + updateFileStatus.value = false; + isUpdateFile.value = false; //记录是否在修改页面时是否有新上传的文件 + } + + fileStatus.value = false; +}; /** 提交表单 */ const submitForm = () => { supplierInputFormRef.value?.validate(async (valid: boolean) => { if (!valid) return; if (form.value.supplierType === '劳务') { - form.value.registeredNumber = [form.value.build1, form.value.build2, form.value.build3, form.value.build4].join(','); + form.value.registeredNumber = [ + form.value.firstBuildingNumber, + form.value.secondBuildingNumber, + form.value.registeredEngineerNumber, + form.value.otherBuildingNumber + ].join(','); form.value.personnelNumber = [ - form.value.personnelNumber1, - form.value.personnelNumber2, - form.value.personnelNumber3, - form.value.personnelNumber4 + form.value.seniorEngineerNumber, + form.value.engineerNumber, + form.value.assistantEngineerNumber, + form.value.otherPersonnelNumber ].join(','); } buttonLoading.value = true; try { if (fileUploadRef.value) { - await fileUploadRef.value.submitUpload().then((res) => { - console.log(res); - if (res == 'noFile') { - proxy?.$modal.msgError('请上传文件'); - return; - } - proxy?.$modal.msgSuccess('操作成功'); - dialog.visible = false; - getList(); - }); + if (form.value.fileId === editFileId.value && !isUpdateFile.value) { + console.log(1111111111); + + editFormData(); + } else { + fileUploadRef.value.submitUpload().then((res) => { + if (res == 'noFile') { + proxy?.$modal.msgError('请上传文件'); + return; + } + proxy?.$modal.msgSuccess('操作成功'); + dialog.visible = false; + getList(); + }); + } } } catch (error) { proxy?.$modal.msgError('提交失败,请重试'); @@ -628,7 +663,14 @@ const submitForm = () => { } }); }; - +const editFormData = async () => { + const res = await updateSupplierInput(form.value); + if ((res.code = 200)) { + proxy?.$modal.msgSuccess('操作成功'); + dialog.visible = false; + getList(); + } +}; /** 删除操作 */ const handleDelete = async (row?: SupplierInputVO) => { const _ids = row?.id || ids.value; diff --git a/src/views/tender/supplierInput/indexEdit.vue b/src/views/tender/supplierInput/indexEdit.vue index b6f6677..4429c53 100644 --- a/src/views/tender/supplierInput/indexEdit.vue +++ b/src/views/tender/supplierInput/indexEdit.vue @@ -104,8 +104,9 @@ <el-col :span="12"> <el-form-item label="近三年营业额" prop="pastThreeYears"> <el-input v-model="form.pastThreeYears" placeholder="请输入近三年营业额" clearable /> - </el-form-item> </el-col - ><el-col :span="12"> + </el-form-item> + </el-col> + <!-- <el-col :span="12"> <el-form-item label="生产许可证编号" prop="safeCode"> <el-input v-model="form.safeCode" placeholder="请输入许可证编号" clearable /> </el-form-item> @@ -118,45 +119,45 @@ <el-form-item label="生产许可证发证日期" prop="safeCertificateValidity"> <el-date-picker v-model="form.safeCertificateValidity" type="date" placeholder="请选择发证日期" /> </el-form-item> - </el-col> + </el-col> --> </el-row> <el-row class="mb-4" v-if="form.supplierType === '劳务'"> <el-col :span="12"> - <el-form-item label="一建建造师" prop="build1"> - <el-input v-model="form.build1" placeholder="请输入一建建造师数量" clearable /> + <el-form-item label="一建建造师" prop="firstBuildingNumber"> + <el-input v-model="form.firstBuildingNumber" placeholder="请输入一建建造师数量" clearable /> </el-form-item> </el-col> <el-col :span="12"> - <el-form-item label="二建建造师" prop="build2"> - <el-input v-model="form.build2" placeholder="请输入二建建造师数量" clearable /> + <el-form-item label="二建建造师" prop="secondBuildingNumber"> + <el-input v-model="form.secondBuildingNumber" placeholder="请输入二建建造师数量" clearable /> </el-form-item> </el-col> <el-col :span="12"> - <el-form-item label="其他(分别写)" prop="build4"> - <el-input v-model="form.build3" placeholder="请输入其他人员数量" clearable /> + <el-form-item label="其他(分别写)" prop="otherBuildingNumber"> + <el-input v-model="form.otherBuildingNumber" placeholder="请输入其他人员数量" clearable /> </el-form-item> </el-col ><el-col :span="12"> - <el-form-item label="注册造价工程师" prop="build3"> - <el-input v-model="form.build4" placeholder="请输入注册造价工程师数量" clearable /> + <el-form-item label="注册造价工程师" prop="registeredEngineerNumber"> + <el-input v-model="form.registeredEngineerNumber" placeholder="请输入注册造价工程师数量" clearable /> </el-form-item> </el-col> </el-row> <el-row :gutter="24" class="mb-4" v-if="form.supplierType === '劳务'"> <el-col :span="12"> - <el-form-item label="高级工程师人数" prop="personnelNumber1"> - <el-input v-model="form.personnelNumber1" placeholder="请输高级工程师数量" clearable /> + <el-form-item label="高级工程师人数" prop="seniorEngineerNumber"> + <el-input v-model="form.seniorEngineerNumber" placeholder="请输高级工程师数量" clearable /> </el-form-item> </el-col ><el-col :span="12"> - <el-form-item label="工程师数量" prop="personnelNumber2"> - <el-input v-model="form.personnelNumber2" placeholder="请输入工程师数量" clearable /> + <el-form-item label="工程师数量" prop="engineerNumber"> + <el-input v-model="form.engineerNumber" placeholder="请输入工程师数量" clearable /> </el-form-item> </el-col ><el-col :span="12"> - <el-form-item label="助理工程师数量" prop="personnelNumber3"> - <el-input v-model="form.personnelNumber3" placeholder="请输入助理工程师数量" clearable /> + <el-form-item label="助理工程师数量" prop="assistantEngineerNumber"> + <el-input v-model="form.assistantEngineerNumber" placeholder="请输入助理工程师数量" clearable /> </el-form-item> </el-col ><el-col :span="12"> - <el-form-item label="其他人员数量" prop="personnelNumber4"> - <el-input v-model="form.personnelNumber4" placeholder="请输入其他人员数量" clearable /> + <el-form-item label="其他人员数量" prop="otherPersonnelNumber"> + <el-input v-model="form.otherPersonnelNumber" placeholder="请输入其他人员数量" clearable /> </el-form-item> </el-col> </el-row> @@ -270,14 +271,14 @@ const initFormData = { inputFile: undefined, state: '0', // 新增默认待审核 // 新增:用于表单输入的单独字段 - build1: undefined, // 一建建造师 - build2: undefined, // 二建建造师 - build3: undefined, // 注册造价工程师 - build4: undefined, // 其他注册人员 - personnelNumber1: undefined, // 高级工程师 - personnelNumber2: undefined, // 工程师 - personnelNumber3: undefined, // 助理工程师 - personnelNumber4: undefined // 其他职称人员 + firstBuildingNumber: undefined, // 一建建造师 + secondBuildingNumber: undefined, // 二建建造师 + registeredEngineerNumber: undefined, // 注册造价工程师 + otherBuildingNumber: undefined, // 其他注册人员 + seniorEngineerNumber: undefined, // 高级工程师 + engineerNumber: undefined, // 工程师 + assistantEngineerNumber: undefined, // 助理工程师 + otherPersonnelNumber: undefined }; const data = reactive<PageData<LeaveForm, LeaveQuery>>({ form: { ...initFormData }, @@ -332,17 +333,27 @@ const getInfo = () => { buttonLoading.value = false; nextTick(async () => { const res = await getSupplierInput(routeParams.value.id); + console.log(res, '------------------res'); + Object.assign(form.value, res.data); - form.value.registeredNumber = form.value.registeredNumber?.split(','); - form.value.build1 = form.value.registeredNumber[0] || ''; - form.value.build2 = form.value.registeredNumber[1] || ''; - form.value.build3 = form.value.registeredNumber[2] || ''; - form.value.build4 = form.value.registeredNumber[3] || ''; - form.value.personnelNumber = form.value.personnelNumber?.split(','); - form.value.personnelNumber1 = form.value.personnelNumber[0] || ''; - form.value.personnelNumber2 = form.value.personnelNumber[1] || ''; - form.value.personnelNumber3 = form.value.personnelNumber[2] || ''; - form.value.personnelNumber4 = form.value.personnelNumber[3] || ''; + // form.value.firstBuildingNumber=res.data.firstBuildingNumber, // 一建建造师 + // secondBuildingNumber: undefined, // 二建建造师 + // registeredEngineerNumber: undefined, // 注册造价工程师 + // otherBuildingNumber: undefined, // 其他注册人员 + // seniorEngineerNumber: undefined, // 高级工程师 + // engineerNumber: undefined, // 工程师 + // assistantEngineerNumber: undefined, // 助理工程师 + // otherPersonnelNumber: undefined + // form.value.registeredNumber = form.value.registeredNumber?.split(','); + // form.value.build1 = form.value.registeredNumber[0] || ''; + // form.value.build2 = form.value.registeredNumber[1] || ''; + // form.value.build3 = form.value.registeredNumber[2] || ''; + // form.value.build4 = form.value.registeredNumber[3] || ''; + // form.value.personnelNumber = form.value.personnelNumber?.split(','); + // form.value.personnelNumber1 = form.value.personnelNumber[0] || ''; + // form.value.personnelNumber2 = form.value.personnelNumber[1] || ''; + // form.value.personnelNumber3 = form.value.personnelNumber[2] || ''; + // form.value.personnelNumber4 = form.value.personnelNumber[3] || ''; loading.value = false; buttonLoading.value = false; diff --git a/vite.config.ts.timestamp-1756395377501-e6f009967ecc8.mjs b/vite.config.ts.timestamp-1756395377501-e6f009967ecc8.mjs deleted file mode 100644 index ec49d52..0000000 --- a/vite.config.ts.timestamp-1756395377501-e6f009967ecc8.mjs +++ /dev/null @@ -1,230 +0,0 @@ -// vite.config.ts -import { loadEnv, defineConfig } from "file:///E:/XNY/new-project/node_modules/vite/dist/node/index.js"; - -// vite/plugins/index.ts -import vue from "file:///E:/XNY/new-project/node_modules/@vitejs/plugin-vue/dist/index.mjs"; - -// vite/plugins/unocss.ts -import UnoCss from "file:///E:/XNY/new-project/node_modules/unocss/dist/vite.mjs"; -var unocss_default = () => { - return UnoCss({ - hmrTopLevelAwait: false - // unocss默认是true,低版本浏览器是不支持的,启动后会报错 - }); -}; - -// vite/plugins/auto-import.ts -import AutoImport from "file:///E:/XNY/new-project/node_modules/unplugin-auto-import/dist/vite.js"; -import { ElementPlusResolver } from "file:///E:/XNY/new-project/node_modules/unplugin-vue-components/dist/resolvers.js"; -import IconsResolver from "file:///E:/XNY/new-project/node_modules/unplugin-icons/dist/resolver.js"; -var __vite_injected_original_dirname = "E:\\XNY\\new-project\\vite\\plugins"; -var auto_import_default = (path3) => { - return AutoImport({ - // 自动导入 Vue 相关函数 - imports: ["vue", "vue-router", "@vueuse/core", "pinia"], - eslintrc: { - enabled: false, - filepath: "./.eslintrc-auto-import.json", - globalsPropValue: true - }, - resolvers: [ - // 自动导入 Element Plus 相关函数ElMessage, ElMessageBox... (带样式) - ElementPlusResolver(), - IconsResolver({ - prefix: "Icon" - }) - ], - vueTemplate: true, - // 是否在 vue 模板中自动导入 - dts: path3.resolve(path3.resolve(__vite_injected_original_dirname, "../../src"), "types", "auto-imports.d.ts") - }); -}; - -// vite/plugins/components.ts -import Components from "file:///E:/XNY/new-project/node_modules/unplugin-vue-components/dist/vite.js"; -import { ElementPlusResolver as ElementPlusResolver2 } from "file:///E:/XNY/new-project/node_modules/unplugin-vue-components/dist/resolvers.js"; -import IconsResolver2 from "file:///E:/XNY/new-project/node_modules/unplugin-icons/dist/resolver.js"; -var __vite_injected_original_dirname2 = "E:\\XNY\\new-project\\vite\\plugins"; -var components_default = (path3) => { - return Components({ - resolvers: [ - // 自动导入 Element Plus 组件 - ElementPlusResolver2(), - // 自动注册图标组件 - IconsResolver2({ - enabledCollections: ["ep"] - }) - ], - dts: path3.resolve(path3.resolve(__vite_injected_original_dirname2, "../../src"), "types", "components.d.ts") - }); -}; - -// vite/plugins/icons.ts -import Icons from "file:///E:/XNY/new-project/node_modules/unplugin-icons/dist/vite.js"; -var icons_default = () => { - return Icons({ - // 自动安装图标库 - autoInstall: true - }); -}; - -// vite/plugins/svg-icon.ts -import { createSvgIconsPlugin } from "file:///E:/XNY/new-project/node_modules/vite-plugin-svg-icons/dist/index.mjs"; -var __vite_injected_original_dirname3 = "E:\\XNY\\new-project\\vite\\plugins"; -var svg_icon_default = (path3, isBuild) => { - return createSvgIconsPlugin({ - // 指定需要缓存的图标文件夹 - iconDirs: [path3.resolve(path3.resolve(__vite_injected_original_dirname3, "../../src"), "assets/icons/svg")], - // 指定symbolId格式 - symbolId: "icon-[dir]-[name]", - svgoOptions: isBuild - }); -}; - -// vite/plugins/compression.ts -import compression from "file:///E:/XNY/new-project/node_modules/vite-plugin-compression/dist/index.mjs"; -var compression_default = (env) => { - const { VITE_BUILD_COMPRESS } = env; - const plugin = []; - if (VITE_BUILD_COMPRESS) { - const compressList = VITE_BUILD_COMPRESS.split(","); - if (compressList.includes("gzip")) { - plugin.push( - compression({ - ext: ".gz", - deleteOriginFile: false - }) - ); - } - if (compressList.includes("brotli")) { - plugin.push( - compression({ - ext: ".br", - algorithm: "brotliCompress", - deleteOriginFile: false - }) - ); - } - } - return plugin; -}; - -// vite/plugins/setup-extend.ts -import setupExtend from "file:///E:/XNY/new-project/node_modules/unplugin-vue-setup-extend-plus/dist/vite.js"; -var setup_extend_default = () => { - return setupExtend({}); -}; - -// vite/plugins/index.ts -import path from "path"; -var plugins_default = (viteEnv, isBuild = false) => { - const vitePlugins = []; - vitePlugins.push(vue()); - vitePlugins.push(unocss_default()); - vitePlugins.push(auto_import_default(path)); - vitePlugins.push(components_default(path)); - vitePlugins.push(compression_default(viteEnv)); - vitePlugins.push(icons_default()); - vitePlugins.push(svg_icon_default(path, isBuild)); - vitePlugins.push(setup_extend_default()); - return vitePlugins; -}; - -// vite.config.ts -import path2 from "path"; -var __vite_injected_original_dirname4 = "E:\\XNY\\new-project"; -var vite_config_default = defineConfig(({ mode, command }) => { - const env = loadEnv(mode, process.cwd()); - return { - // 部署生产环境和开发环境下的URL。 - // 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上 - // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 - base: env.VITE_APP_CONTEXT_PATH, - resolve: { - alias: { - "~": path2.resolve(__vite_injected_original_dirname4, "./"), - "@": path2.resolve(__vite_injected_original_dirname4, "./src") - }, - extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue", ".tif"] - }, - // https://cn.vitejs.dev/config/#resolve-extensions - plugins: plugins_default(env, command === "build"), - server: { - host: "0.0.0.0", - port: Number(env.VITE_APP_PORT), - open: true, - proxy: { - [env.VITE_APP_BASE_API]: { - target: "http://localhost:8899", - changeOrigin: true, - ws: true, - rewrite: (path3) => path3.replace(new RegExp("^" + env.VITE_APP_BASE_API), "") - }, - "/warm-flow-ui": { - target: env.VITE_APP_BASE_API, - changeOrigin: true, - ws: true, - rewrite: (path3) => path3.replace(new RegExp("^" + env.VITE_APP_BASE_API), "") - }, - "/warm-flow": { - target: env.VITE_APP_BASE_API, - changeOrigin: true, - ws: true, - rewrite: (path3) => path3.replace(new RegExp("^" + env.VITE_APP_BASE_API), "") - }, - "/workflow": { - target: env.VITE_APP_BASE_API, - changeOrigin: true, - ws: true, - rewrite: (path3) => path3.replace(new RegExp("^" + env.VITE_APP_BASE_API), "") - }, - "/auth": { - target: env.VITE_APP_BASE_API, - changeOrigin: true, - ws: true, - rewrite: (path3) => path3.replace(new RegExp("^" + env.VITE_APP_BASE_API), "") - } - } - }, - css: { - preprocessorOptions: { - scss: { - javascriptEnabled: true - } - }, - postcss: { - plugins: [ - { - postcssPlugin: "internal:charset-removal", - AtRule: { - charset: (atRule) => { - if (atRule.name === "charset") { - atRule.remove(); - } - } - } - } - ] - } - }, - // 预编译 - optimizeDeps: { - include: [ - "vue", - "vue-router", - "pinia", - "axios", - "@vueuse/core", - "echarts", - "vue-i18n", - "@vueup/vue-quill", - "image-conversion", - "element-plus/es/components/**/css" - ] - } - }; -}); -export { - vite_config_default as default -}; -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiLCAidml0ZS9wbHVnaW5zL2luZGV4LnRzIiwgInZpdGUvcGx1Z2lucy91bm9jc3MudHMiLCAidml0ZS9wbHVnaW5zL2F1dG8taW1wb3J0LnRzIiwgInZpdGUvcGx1Z2lucy9jb21wb25lbnRzLnRzIiwgInZpdGUvcGx1Z2lucy9pY29ucy50cyIsICJ2aXRlL3BsdWdpbnMvc3ZnLWljb24udHMiLCAidml0ZS9wbHVnaW5zL2NvbXByZXNzaW9uLnRzIiwgInZpdGUvcGx1Z2lucy9zZXR1cC1leHRlbmQudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJFOlxcXFxYTllcXFxcbmV3LXByb2plY3RcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkU6XFxcXFhOWVxcXFxuZXctcHJvamVjdFxcXFx2aXRlLmNvbmZpZy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vRTovWE5ZL25ldy1wcm9qZWN0L3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgVXNlckNvbmZpZywgQ29uZmlnRW52LCBsb2FkRW52LCBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJztcclxuXHJcbmltcG9ydCBjcmVhdGVQbHVnaW5zIGZyb20gJy4vdml0ZS9wbHVnaW5zJztcclxuXHJcbmltcG9ydCBwYXRoIGZyb20gJ3BhdGgnO1xyXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoKHsgbW9kZSwgY29tbWFuZCB9OiBDb25maWdFbnYpOiBVc2VyQ29uZmlnID0+IHtcclxuICBjb25zdCBlbnYgPSBsb2FkRW52KG1vZGUsIHByb2Nlc3MuY3dkKCkpO1xyXG4gIHJldHVybiB7XHJcbiAgICAvLyBcdTkwRThcdTdGNzJcdTc1MUZcdTRFQTdcdTczQUZcdTU4ODNcdTU0OENcdTVGMDBcdTUzRDFcdTczQUZcdTU4ODNcdTRFMEJcdTc2ODRVUkxcdTMwMDJcclxuICAgIC8vIFx1OUVEOFx1OEJBNFx1NjBDNVx1NTFCNVx1NEUwQlx1RkYwQ3ZpdGUgXHU0RjFBXHU1MDQ3XHU4QkJFXHU0RjYwXHU3Njg0XHU1RTk0XHU3NTI4XHU2NjJGXHU4OEFCXHU5MEU4XHU3RjcyXHU1NzI4XHU0RTAwXHU0RTJBXHU1N0RGXHU1NDBEXHU3Njg0XHU2ODM5XHU4REVGXHU1Rjg0XHU0RTBBXHJcbiAgICAvLyBcdTRGOEJcdTU5ODIgaHR0cHM6Ly93d3cucnVveWkudmlwL1x1MzAwMlx1NTk4Mlx1Njc5Q1x1NUU5NFx1NzUyOFx1ODhBQlx1OTBFOFx1N0Y3Mlx1NTcyOFx1NEUwMFx1NEUyQVx1NUI1MFx1OERFRlx1NUY4NFx1NEUwQVx1RkYwQ1x1NEY2MFx1NUMzMVx1OTcwMFx1ODk4MVx1NzUyOFx1OEZEOVx1NEUyQVx1OTAwOVx1OTg3OVx1NjMwN1x1NUI5QVx1OEZEOVx1NEUyQVx1NUI1MFx1OERFRlx1NUY4NFx1MzAwMlx1NEY4Qlx1NTk4Mlx1RkYwQ1x1NTk4Mlx1Njc5Q1x1NEY2MFx1NzY4NFx1NUU5NFx1NzUyOFx1ODhBQlx1OTBFOFx1N0Y3Mlx1NTcyOCBodHRwczovL3d3dy5ydW95aS52aXAvYWRtaW4vXHVGRjBDXHU1MjE5XHU4QkJFXHU3RjZFIGJhc2VVcmwgXHU0RTNBIC9hZG1pbi9cdTMwMDJcclxuICAgIGJhc2U6IGVudi5WSVRFX0FQUF9DT05URVhUX1BBVEgsXHJcbiAgICByZXNvbHZlOiB7XHJcbiAgICAgIGFsaWFzOiB7XHJcbiAgICAgICAgJ34nOiBwYXRoLnJlc29sdmUoX19kaXJuYW1lLCAnLi8nKSxcclxuICAgICAgICAnQCc6IHBhdGgucmVzb2x2ZShfX2Rpcm5hbWUsICcuL3NyYycpXHJcbiAgICAgIH0sXHJcbiAgICAgIGV4dGVuc2lvbnM6IFsnLm1qcycsICcuanMnLCAnLnRzJywgJy5qc3gnLCAnLnRzeCcsICcuanNvbicsICcudnVlJywgJy50aWYnXVxyXG4gICAgfSxcclxuICAgIC8vIGh0dHBzOi8vY24udml0ZWpzLmRldi9jb25maWcvI3Jlc29sdmUtZXh0ZW5zaW9uc1xyXG4gICAgcGx1Z2luczogY3JlYXRlUGx1Z2lucyhlbnYsIGNvbW1hbmQgPT09ICdidWlsZCcpLFxyXG4gICAgc2VydmVyOiB7XHJcbiAgICAgIGhvc3Q6ICcwLjAuMC4wJyxcclxuICAgICAgcG9ydDogTnVtYmVyKGVudi5WSVRFX0FQUF9QT1JUKSxcclxuICAgICAgb3BlbjogdHJ1ZSxcclxuICAgICAgcHJveHk6IHtcclxuICAgICAgICBbZW52LlZJVEVfQVBQX0JBU0VfQVBJXToge1xyXG4gICAgICAgICAgdGFyZ2V0OiAnaHR0cDovL2xvY2FsaG9zdDo4ODk5JyxcclxuICAgICAgICAgIGNoYW5nZU9yaWdpbjogdHJ1ZSxcclxuICAgICAgICAgIHdzOiB0cnVlLFxyXG4gICAgICAgICAgcmV3cml0ZTogKHBhdGgpID0+IHBhdGgucmVwbGFjZShuZXcgUmVnRXhwKCdeJyArIGVudi5WSVRFX0FQUF9CQVNFX0FQSSksICcnKVxyXG4gICAgICAgIH0sXHJcbiAgICAgICAgJy93YXJtLWZsb3ctdWknOiB7XHJcbiAgICAgICAgICB0YXJnZXQ6IGVudi5WSVRFX0FQUF9CQVNFX0FQSSxcclxuICAgICAgICAgIGNoYW5nZU9yaWdpbjogdHJ1ZSxcclxuICAgICAgICAgIHdzOiB0cnVlLFxyXG4gICAgICAgICAgcmV3cml0ZTogKHBhdGgpID0+IHBhdGgucmVwbGFjZShuZXcgUmVnRXhwKCdeJyArIGVudi5WSVRFX0FQUF9CQVNFX0FQSSksICcnKVxyXG4gICAgICAgIH0sXHJcbiAgICAgICAgJy93YXJtLWZsb3cnOiB7XHJcbiAgICAgICAgICB0YXJnZXQ6IGVudi5WSVRFX0FQUF9CQVNFX0FQSSxcclxuICAgICAgICAgIGNoYW5nZU9yaWdpbjogdHJ1ZSxcclxuICAgICAgICAgIHdzOiB0cnVlLFxyXG4gICAgICAgICAgcmV3cml0ZTogKHBhdGgpID0+IHBhdGgucmVwbGFjZShuZXcgUmVnRXhwKCdeJyArIGVudi5WSVRFX0FQUF9CQVNFX0FQSSksICcnKVxyXG4gICAgICAgIH0sXHJcbiAgICAgICAgJy93b3JrZmxvdyc6IHtcclxuICAgICAgICAgIHRhcmdldDogZW52LlZJVEVfQVBQX0JBU0VfQVBJLFxyXG4gICAgICAgICAgY2hhbmdlT3JpZ2luOiB0cnVlLFxyXG4gICAgICAgICAgd3M6IHRydWUsXHJcbiAgICAgICAgICByZXdyaXRlOiAocGF0aCkgPT4gcGF0aC5yZXBsYWNlKG5ldyBSZWdFeHAoJ14nICsgZW52LlZJVEVfQVBQX0JBU0VfQVBJKSwgJycpXHJcbiAgICAgICAgfSxcclxuICAgICAgICAgJy9hdXRoJzoge1xyXG4gICAgICAgICAgdGFyZ2V0OiBlbnYuVklURV9BUFBfQkFTRV9BUEksXHJcbiAgICAgICAgICBjaGFuZ2VPcmlnaW46IHRydWUsXHJcbiAgICAgICAgICB3czogdHJ1ZSxcclxuICAgICAgICAgIHJld3JpdGU6IChwYXRoKSA9PiBwYXRoLnJlcGxhY2UobmV3IFJlZ0V4cCgnXicgKyBlbnYuVklURV9BUFBfQkFTRV9BUEkpLCAnJylcclxuICAgICAgICB9XHJcbiAgICAgIH1cclxuICAgIH0sXHJcbiAgICBjc3M6IHtcclxuICAgICAgcHJlcHJvY2Vzc29yT3B0aW9uczoge1xyXG4gICAgICAgIHNjc3M6IHtcclxuICAgICAgICAgIGphdmFzY3JpcHRFbmFibGVkOiB0cnVlXHJcbiAgICAgICAgfVxyXG4gICAgICB9LFxyXG4gICAgICBwb3N0Y3NzOiB7XHJcbiAgICAgICAgcGx1Z2luczogW1xyXG4gICAgICAgICAge1xyXG4gICAgICAgICAgICBwb3N0Y3NzUGx1Z2luOiAnaW50ZXJuYWw6Y2hhcnNldC1yZW1vdmFsJyxcclxuICAgICAgICAgICAgQXRSdWxlOiB7XHJcbiAgICAgICAgICAgICAgY2hhcnNldDogKGF0UnVsZSkgPT4ge1xyXG4gICAgICAgICAgICAgICAgaWYgKGF0UnVsZS5uYW1lID09PSAnY2hhcnNldCcpIHtcclxuICAgICAgICAgICAgICAgICAgYXRSdWxlLnJlbW92ZSgpO1xyXG4gICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgfVxyXG4gICAgICAgIF1cclxuICAgICAgfVxyXG4gICAgfSxcclxuICAgIC8vIFx1OTg4NFx1N0YxNlx1OEJEMVxyXG4gICAgb3B0aW1pemVEZXBzOiB7XHJcbiAgICAgIGluY2x1ZGU6IFtcclxuICAgICAgICAndnVlJyxcclxuICAgICAgICAndnVlLXJvdXRlcicsXHJcbiAgICAgICAgJ3BpbmlhJyxcclxuICAgICAgICAnYXhpb3MnLFxyXG4gICAgICAgICdAdnVldXNlL2NvcmUnLFxyXG4gICAgICAgICdlY2hhcnRzJyxcclxuICAgICAgICAndnVlLWkxOG4nLFxyXG4gICAgICAgICdAdnVldXAvdnVlLXF1aWxsJyxcclxuICAgICAgICAnaW1hZ2UtY29udmVyc2lvbicsXHJcbiAgICAgICAgJ2VsZW1lbnQtcGx1cy9lcy9jb21wb25lbnRzLyoqL2NzcydcclxuICAgICAgXVxyXG4gICAgfVxyXG4gIH07XHJcbn0pO1xyXG4iLCAiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIkU6XFxcXFhOWVxcXFxuZXctcHJvamVjdFxcXFx2aXRlXFxcXHBsdWdpbnNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkU6XFxcXFhOWVxcXFxuZXctcHJvamVjdFxcXFx2aXRlXFxcXHBsdWdpbnNcXFxcaW5kZXgudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL0U6L1hOWS9uZXctcHJvamVjdC92aXRlL3BsdWdpbnMvaW5kZXgudHNcIjtpbXBvcnQgdnVlIGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZSc7XHJcbmltcG9ydCBjcmVhdGVVbm9Dc3MgZnJvbSAnLi91bm9jc3MnO1xyXG5pbXBvcnQgY3JlYXRlQXV0b0ltcG9ydCBmcm9tICcuL2F1dG8taW1wb3J0JztcclxuaW1wb3J0IGNyZWF0ZUNvbXBvbmVudHMgZnJvbSAnLi9jb21wb25lbnRzJztcclxuaW1wb3J0IGNyZWF0ZUljb25zIGZyb20gJy4vaWNvbnMnO1xyXG5pbXBvcnQgY3JlYXRlU3ZnSWNvbnNQbHVnaW4gZnJvbSAnLi9zdmctaWNvbic7XHJcbmltcG9ydCBjcmVhdGVDb21wcmVzc2lvbiBmcm9tICcuL2NvbXByZXNzaW9uJztcclxuaW1wb3J0IGNyZWF0ZVNldHVwRXh0ZW5kIGZyb20gJy4vc2V0dXAtZXh0ZW5kJztcclxuaW1wb3J0IHBhdGggZnJvbSAncGF0aCc7XHJcblxyXG5leHBvcnQgZGVmYXVsdCAodml0ZUVudjogYW55LCBpc0J1aWxkID0gZmFsc2UpOiBbXSA9PiB7XHJcbiAgY29uc3Qgdml0ZVBsdWdpbnM6IGFueSA9IFtdO1xyXG4gIHZpdGVQbHVnaW5zLnB1c2godnVlKCkpO1xyXG4gIHZpdGVQbHVnaW5zLnB1c2goY3JlYXRlVW5vQ3NzKCkpO1xyXG4gIHZpdGVQbHVnaW5zLnB1c2goY3JlYXRlQXV0b0ltcG9ydChwYXRoKSk7XHJcbiAgdml0ZVBsdWdpbnMucHVzaChjcmVhdGVDb21wb25lbnRzKHBhdGgpKTtcclxuICB2aXRlUGx1Z2lucy5wdXNoKGNyZWF0ZUNvbXByZXNzaW9uKHZpdGVFbnYpKTtcclxuICB2aXRlUGx1Z2lucy5wdXNoKGNyZWF0ZUljb25zKCkpO1xyXG4gIHZpdGVQbHVnaW5zLnB1c2goY3JlYXRlU3ZnSWNvbnNQbHVnaW4ocGF0aCwgaXNCdWlsZCkpO1xyXG4gIHZpdGVQbHVnaW5zLnB1c2goY3JlYXRlU2V0dXBFeHRlbmQoKSk7XHJcbiAgcmV0dXJuIHZpdGVQbHVnaW5zO1xyXG59O1xyXG4iLCAiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIkU6XFxcXFhOWVxcXFxuZXctcHJvamVjdFxcXFx2aXRlXFxcXHBsdWdpbnNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkU6XFxcXFhOWVxcXFxuZXctcHJvamVjdFxcXFx2aXRlXFxcXHBsdWdpbnNcXFxcdW5vY3NzLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9FOi9YTlkvbmV3LXByb2plY3Qvdml0ZS9wbHVnaW5zL3Vub2Nzcy50c1wiO2ltcG9ydCBVbm9Dc3MgZnJvbSAndW5vY3NzL3ZpdGUnO1xyXG5cclxuZXhwb3J0IGRlZmF1bHQgKCkgPT4ge1xyXG4gIHJldHVybiBVbm9Dc3Moe1xyXG4gICAgaG1yVG9wTGV2ZWxBd2FpdDogZmFsc2UgLy8gdW5vY3NzXHU5RUQ4XHU4QkE0XHU2NjJGdHJ1ZVx1RkYwQ1x1NEY0RVx1NzI0OFx1NjcyQ1x1NkQ0Rlx1ODlDOFx1NTY2OFx1NjYyRlx1NEUwRFx1NjUyRlx1NjMwMVx1NzY4NFx1RkYwQ1x1NTQyRlx1NTJBOFx1NTQwRVx1NEYxQVx1NjJBNVx1OTUxOVxyXG4gIH0pO1xyXG59O1xyXG4iLCAiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIkU6XFxcXFhOWVxcXFxuZXctcHJvamVjdFxcXFx2aXRlXFxcXHBsdWdpbnNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkU6XFxcXFhOWVxcXFxuZXctcHJvamVjdFxcXFx2aXRlXFxcXHBsdWdpbnNcXFxcYXV0by1pbXBvcnQudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL0U6L1hOWS9uZXctcHJvamVjdC92aXRlL3BsdWdpbnMvYXV0by1pbXBvcnQudHNcIjtpbXBvcnQgQXV0b0ltcG9ydCBmcm9tICd1bnBsdWdpbi1hdXRvLWltcG9ydC92aXRlJztcclxuaW1wb3J0IHsgRWxlbWVudFBsdXNSZXNvbHZlciB9IGZyb20gJ3VucGx1Z2luLXZ1ZS1jb21wb25lbnRzL3Jlc29sdmVycyc7XHJcbmltcG9ydCBJY29uc1Jlc29sdmVyIGZyb20gJ3VucGx1Z2luLWljb25zL3Jlc29sdmVyJztcclxuXHJcbmV4cG9ydCBkZWZhdWx0IChwYXRoOiBhbnkpID0+IHtcclxuICByZXR1cm4gQXV0b0ltcG9ydCh7XHJcbiAgICAvLyBcdTgxRUFcdTUyQThcdTVCRkNcdTUxNjUgVnVlIFx1NzZGOFx1NTE3M1x1NTFGRFx1NjU3MFxyXG4gICAgaW1wb3J0czogWyd2dWUnLCAndnVlLXJvdXRlcicsICdAdnVldXNlL2NvcmUnLCAncGluaWEnXSxcclxuICAgIGVzbGludHJjOiB7XHJcbiAgICAgIGVuYWJsZWQ6IGZhbHNlLFxyXG4gICAgICBmaWxlcGF0aDogJy4vLmVzbGludHJjLWF1dG8taW1wb3J0Lmpzb24nLFxyXG4gICAgICBnbG9iYWxzUHJvcFZhbHVlOiB0cnVlXHJcbiAgICB9LFxyXG4gICAgcmVzb2x2ZXJzOiBbXHJcbiAgICAgIC8vIFx1ODFFQVx1NTJBOFx1NUJGQ1x1NTE2NSBFbGVtZW50IFBsdXMgXHU3NkY4XHU1MTczXHU1MUZEXHU2NTcwRWxNZXNzYWdlLCBFbE1lc3NhZ2VCb3guLi4gKFx1NUUyNlx1NjgzN1x1NUYwRilcclxuICAgICAgRWxlbWVudFBsdXNSZXNvbHZlcigpLFxyXG4gICAgICBJY29uc1Jlc29sdmVyKHtcclxuICAgICAgICBwcmVmaXg6ICdJY29uJ1xyXG4gICAgICB9KVxyXG4gICAgXSxcclxuICAgIHZ1ZVRlbXBsYXRlOiB0cnVlLCAvLyBcdTY2MkZcdTU0MjZcdTU3MjggdnVlIFx1NkEyMVx1Njc3Rlx1NEUyRFx1ODFFQVx1NTJBOFx1NUJGQ1x1NTE2NVxyXG4gICAgZHRzOiBwYXRoLnJlc29sdmUocGF0aC5yZXNvbHZlKF9fZGlybmFtZSwgJy4uLy4uL3NyYycpLCAndHlwZXMnLCAnYXV0by1pbXBvcnRzLmQudHMnKVxyXG4gIH0pO1xyXG59O1xyXG4iLCAiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIkU6XFxcXFhOWVxcXFxuZXctcHJvamVjdFxcXFx2aXRlXFxcXHBsdWdpbnNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkU6XFxcXFhOWVxcXFxuZXctcHJvamVjdFxcXFx2aXRlXFxcXHBsdWdpbnNcXFxcY29tcG9uZW50cy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vRTovWE5ZL25ldy1wcm9qZWN0L3ZpdGUvcGx1Z2lucy9jb21wb25lbnRzLnRzXCI7aW1wb3J0IENvbXBvbmVudHMgZnJvbSAndW5wbHVnaW4tdnVlLWNvbXBvbmVudHMvdml0ZSc7XHJcbmltcG9ydCB7IEVsZW1lbnRQbHVzUmVzb2x2ZXIgfSBmcm9tICd1bnBsdWdpbi12dWUtY29tcG9uZW50cy9yZXNvbHZlcnMnO1xyXG5pbXBvcnQgSWNvbnNSZXNvbHZlciBmcm9tICd1bnBsdWdpbi1pY29ucy9yZXNvbHZlcic7XHJcblxyXG5leHBvcnQgZGVmYXVsdCAocGF0aDogYW55KSA9PiB7XHJcbiAgcmV0dXJuIENvbXBvbmVudHMoe1xyXG4gICAgcmVzb2x2ZXJzOiBbXHJcbiAgICAgIC8vIFx1ODFFQVx1NTJBOFx1NUJGQ1x1NTE2NSBFbGVtZW50IFBsdXMgXHU3RUM0XHU0RUY2XHJcbiAgICAgIEVsZW1lbnRQbHVzUmVzb2x2ZXIoKSxcclxuICAgICAgLy8gXHU4MUVBXHU1MkE4XHU2Q0U4XHU1MThDXHU1NkZFXHU2ODA3XHU3RUM0XHU0RUY2XHJcbiAgICAgIEljb25zUmVzb2x2ZXIoe1xyXG4gICAgICAgIGVuYWJsZWRDb2xsZWN0aW9uczogWydlcCddXHJcbiAgICAgIH0pXHJcbiAgICBdLFxyXG4gICAgZHRzOiBwYXRoLnJlc29sdmUocGF0aC5yZXNvbHZlKF9fZGlybmFtZSwgJy4uLy4uL3NyYycpLCAndHlwZXMnLCAnY29tcG9uZW50cy5kLnRzJylcclxuICB9KTtcclxufTtcclxuIiwgImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJFOlxcXFxYTllcXFxcbmV3LXByb2plY3RcXFxcdml0ZVxcXFxwbHVnaW5zXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCJFOlxcXFxYTllcXFxcbmV3LXByb2plY3RcXFxcdml0ZVxcXFxwbHVnaW5zXFxcXGljb25zLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9FOi9YTlkvbmV3LXByb2plY3Qvdml0ZS9wbHVnaW5zL2ljb25zLnRzXCI7aW1wb3J0IEljb25zIGZyb20gJ3VucGx1Z2luLWljb25zL3ZpdGUnO1xyXG5cclxuZXhwb3J0IGRlZmF1bHQgKCkgPT4ge1xyXG4gIHJldHVybiBJY29ucyh7XHJcbiAgICAvLyBcdTgxRUFcdTUyQThcdTVCODlcdTg4QzVcdTU2RkVcdTY4MDdcdTVFOTNcclxuICAgIGF1dG9JbnN0YWxsOiB0cnVlXHJcbiAgfSk7XHJcbn07XHJcbiIsICJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiRTpcXFxcWE5ZXFxcXG5ldy1wcm9qZWN0XFxcXHZpdGVcXFxccGx1Z2luc1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiRTpcXFxcWE5ZXFxcXG5ldy1wcm9qZWN0XFxcXHZpdGVcXFxccGx1Z2luc1xcXFxzdmctaWNvbi50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vRTovWE5ZL25ldy1wcm9qZWN0L3ZpdGUvcGx1Z2lucy9zdmctaWNvbi50c1wiO2ltcG9ydCB7IGNyZWF0ZVN2Z0ljb25zUGx1Z2luIH0gZnJvbSAndml0ZS1wbHVnaW4tc3ZnLWljb25zJztcclxuZXhwb3J0IGRlZmF1bHQgKHBhdGg6IGFueSwgaXNCdWlsZDogYm9vbGVhbikgPT4ge1xyXG4gIHJldHVybiBjcmVhdGVTdmdJY29uc1BsdWdpbih7XHJcbiAgICAvLyBcdTYzMDdcdTVCOUFcdTk3MDBcdTg5ODFcdTdGMTNcdTVCNThcdTc2ODRcdTU2RkVcdTY4MDdcdTY1ODdcdTRFRjZcdTU5MzlcclxuICAgIGljb25EaXJzOiBbcGF0aC5yZXNvbHZlKHBhdGgucmVzb2x2ZShfX2Rpcm5hbWUsICcuLi8uLi9zcmMnKSwgJ2Fzc2V0cy9pY29ucy9zdmcnKV0sXHJcbiAgICAvLyBcdTYzMDdcdTVCOUFzeW1ib2xJZFx1NjgzQ1x1NUYwRlxyXG4gICAgc3ltYm9sSWQ6ICdpY29uLVtkaXJdLVtuYW1lXScsXHJcbiAgICBzdmdvT3B0aW9uczogaXNCdWlsZFxyXG4gIH0pO1xyXG59O1xyXG4iLCAiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIkU6XFxcXFhOWVxcXFxuZXctcHJvamVjdFxcXFx2aXRlXFxcXHBsdWdpbnNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkU6XFxcXFhOWVxcXFxuZXctcHJvamVjdFxcXFx2aXRlXFxcXHBsdWdpbnNcXFxcY29tcHJlc3Npb24udHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL0U6L1hOWS9uZXctcHJvamVjdC92aXRlL3BsdWdpbnMvY29tcHJlc3Npb24udHNcIjtpbXBvcnQgY29tcHJlc3Npb24gZnJvbSAndml0ZS1wbHVnaW4tY29tcHJlc3Npb24nO1xyXG5cclxuZXhwb3J0IGRlZmF1bHQgKGVudjogYW55KSA9PiB7XHJcbiAgY29uc3QgeyBWSVRFX0JVSUxEX0NPTVBSRVNTIH0gPSBlbnY7XHJcbiAgY29uc3QgcGx1Z2luOiBhbnlbXSA9IFtdO1xyXG4gIGlmIChWSVRFX0JVSUxEX0NPTVBSRVNTKSB7XHJcbiAgICBjb25zdCBjb21wcmVzc0xpc3QgPSBWSVRFX0JVSUxEX0NPTVBSRVNTLnNwbGl0KCcsJyk7XHJcbiAgICBpZiAoY29tcHJlc3NMaXN0LmluY2x1ZGVzKCdnemlwJykpIHtcclxuICAgICAgLy8gaHR0cDovL2RvYy5ydW95aS52aXAvcnVveWktdnVlL290aGVyL2ZhcS5odG1sI1x1NEY3Rlx1NzUyOGd6aXBcdTg5RTNcdTUzOEJcdTdGMjlcdTk3NTlcdTYwMDFcdTY1ODdcdTRFRjZcclxuICAgICAgcGx1Z2luLnB1c2goXHJcbiAgICAgICAgY29tcHJlc3Npb24oe1xyXG4gICAgICAgICAgZXh0OiAnLmd6JyxcclxuICAgICAgICAgIGRlbGV0ZU9yaWdpbkZpbGU6IGZhbHNlXHJcbiAgICAgICAgfSlcclxuICAgICAgKTtcclxuICAgIH1cclxuICAgIGlmIChjb21wcmVzc0xpc3QuaW5jbHVkZXMoJ2Jyb3RsaScpKSB7XHJcbiAgICAgIHBsdWdpbi5wdXNoKFxyXG4gICAgICAgIGNvbXByZXNzaW9uKHtcclxuICAgICAgICAgIGV4dDogJy5icicsXHJcbiAgICAgICAgICBhbGdvcml0aG06ICdicm90bGlDb21wcmVzcycsXHJcbiAgICAgICAgICBkZWxldGVPcmlnaW5GaWxlOiBmYWxzZVxyXG4gICAgICAgIH0pXHJcbiAgICAgICk7XHJcbiAgICB9XHJcbiAgfVxyXG4gIHJldHVybiBwbHVnaW47XHJcbn07XHJcbiIsICJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiRTpcXFxcWE5ZXFxcXG5ldy1wcm9qZWN0XFxcXHZpdGVcXFxccGx1Z2luc1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiRTpcXFxcWE5ZXFxcXG5ldy1wcm9qZWN0XFxcXHZpdGVcXFxccGx1Z2luc1xcXFxzZXR1cC1leHRlbmQudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL0U6L1hOWS9uZXctcHJvamVjdC92aXRlL3BsdWdpbnMvc2V0dXAtZXh0ZW5kLnRzXCI7aW1wb3J0IHNldHVwRXh0ZW5kIGZyb20gJ3VucGx1Z2luLXZ1ZS1zZXR1cC1leHRlbmQtcGx1cy92aXRlJztcclxuXHJcbmV4cG9ydCBkZWZhdWx0ICgpID0+IHtcclxuICByZXR1cm4gc2V0dXBFeHRlbmQoe30pO1xyXG59O1xyXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQThPLFNBQWdDLFNBQVMsb0JBQW9COzs7QUNBOUIsT0FBTyxTQUFTOzs7QUNBZCxPQUFPLFlBQVk7QUFFbFMsSUFBTyxpQkFBUSxNQUFNO0FBQ25CLFNBQU8sT0FBTztBQUFBLElBQ1osa0JBQWtCO0FBQUE7QUFBQSxFQUNwQixDQUFDO0FBQ0g7OztBQ055UixPQUFPLGdCQUFnQjtBQUNoVCxTQUFTLDJCQUEyQjtBQUNwQyxPQUFPLG1CQUFtQjtBQUYxQixJQUFNLG1DQUFtQztBQUl6QyxJQUFPLHNCQUFRLENBQUNBLFVBQWM7QUFDNUIsU0FBTyxXQUFXO0FBQUE7QUFBQSxJQUVoQixTQUFTLENBQUMsT0FBTyxjQUFjLGdCQUFnQixPQUFPO0FBQUEsSUFDdEQsVUFBVTtBQUFBLE1BQ1IsU0FBUztBQUFBLE1BQ1QsVUFBVTtBQUFBLE1BQ1Ysa0JBQWtCO0FBQUEsSUFDcEI7QUFBQSxJQUNBLFdBQVc7QUFBQTtBQUFBLE1BRVQsb0JBQW9CO0FBQUEsTUFDcEIsY0FBYztBQUFBLFFBQ1osUUFBUTtBQUFBLE1BQ1YsQ0FBQztBQUFBLElBQ0g7QUFBQSxJQUNBLGFBQWE7QUFBQTtBQUFBLElBQ2IsS0FBS0EsTUFBSyxRQUFRQSxNQUFLLFFBQVEsa0NBQVcsV0FBVyxHQUFHLFNBQVMsbUJBQW1CO0FBQUEsRUFDdEYsQ0FBQztBQUNIOzs7QUN2QnVSLE9BQU8sZ0JBQWdCO0FBQzlTLFNBQVMsdUJBQUFDLDRCQUEyQjtBQUNwQyxPQUFPQyxvQkFBbUI7QUFGMUIsSUFBTUMsb0NBQW1DO0FBSXpDLElBQU8scUJBQVEsQ0FBQ0MsVUFBYztBQUM1QixTQUFPLFdBQVc7QUFBQSxJQUNoQixXQUFXO0FBQUE7QUFBQSxNQUVUQyxxQkFBb0I7QUFBQTtBQUFBLE1BRXBCQyxlQUFjO0FBQUEsUUFDWixvQkFBb0IsQ0FBQyxJQUFJO0FBQUEsTUFDM0IsQ0FBQztBQUFBLElBQ0g7QUFBQSxJQUNBLEtBQUtGLE1BQUssUUFBUUEsTUFBSyxRQUFRRyxtQ0FBVyxXQUFXLEdBQUcsU0FBUyxpQkFBaUI7QUFBQSxFQUNwRixDQUFDO0FBQ0g7OztBQ2hCNlEsT0FBTyxXQUFXO0FBRS9SLElBQU8sZ0JBQVEsTUFBTTtBQUNuQixTQUFPLE1BQU07QUFBQTtBQUFBLElBRVgsYUFBYTtBQUFBLEVBQ2YsQ0FBQztBQUNIOzs7QUNQbVIsU0FBUyw0QkFBNEI7QUFBeFQsSUFBTUMsb0NBQW1DO0FBQ3pDLElBQU8sbUJBQVEsQ0FBQ0MsT0FBVyxZQUFxQjtBQUM5QyxTQUFPLHFCQUFxQjtBQUFBO0FBQUEsSUFFMUIsVUFBVSxDQUFDQSxNQUFLLFFBQVFBLE1BQUssUUFBUUMsbUNBQVcsV0FBVyxHQUFHLGtCQUFrQixDQUFDO0FBQUE7QUFBQSxJQUVqRixVQUFVO0FBQUEsSUFDVixhQUFhO0FBQUEsRUFDZixDQUFDO0FBQ0g7OztBQ1R5UixPQUFPLGlCQUFpQjtBQUVqVCxJQUFPLHNCQUFRLENBQUMsUUFBYTtBQUMzQixRQUFNLEVBQUUsb0JBQW9CLElBQUk7QUFDaEMsUUFBTSxTQUFnQixDQUFDO0FBQ3ZCLE1BQUkscUJBQXFCO0FBQ3ZCLFVBQU0sZUFBZSxvQkFBb0IsTUFBTSxHQUFHO0FBQ2xELFFBQUksYUFBYSxTQUFTLE1BQU0sR0FBRztBQUVqQyxhQUFPO0FBQUEsUUFDTCxZQUFZO0FBQUEsVUFDVixLQUFLO0FBQUEsVUFDTCxrQkFBa0I7QUFBQSxRQUNwQixDQUFDO0FBQUEsTUFDSDtBQUFBLElBQ0Y7QUFDQSxRQUFJLGFBQWEsU0FBUyxRQUFRLEdBQUc7QUFDbkMsYUFBTztBQUFBLFFBQ0wsWUFBWTtBQUFBLFVBQ1YsS0FBSztBQUFBLFVBQ0wsV0FBVztBQUFBLFVBQ1gsa0JBQWtCO0FBQUEsUUFDcEIsQ0FBQztBQUFBLE1BQ0g7QUFBQSxJQUNGO0FBQUEsRUFDRjtBQUNBLFNBQU87QUFDVDs7O0FDM0IyUixPQUFPLGlCQUFpQjtBQUVuVCxJQUFPLHVCQUFRLE1BQU07QUFDbkIsU0FBTyxZQUFZLENBQUMsQ0FBQztBQUN2Qjs7O0FQSUEsT0FBTyxVQUFVO0FBRWpCLElBQU8sa0JBQVEsQ0FBQyxTQUFjLFVBQVUsVUFBYztBQUNwRCxRQUFNLGNBQW1CLENBQUM7QUFDMUIsY0FBWSxLQUFLLElBQUksQ0FBQztBQUN0QixjQUFZLEtBQUssZUFBYSxDQUFDO0FBQy9CLGNBQVksS0FBSyxvQkFBaUIsSUFBSSxDQUFDO0FBQ3ZDLGNBQVksS0FBSyxtQkFBaUIsSUFBSSxDQUFDO0FBQ3ZDLGNBQVksS0FBSyxvQkFBa0IsT0FBTyxDQUFDO0FBQzNDLGNBQVksS0FBSyxjQUFZLENBQUM7QUFDOUIsY0FBWSxLQUFLLGlCQUFxQixNQUFNLE9BQU8sQ0FBQztBQUNwRCxjQUFZLEtBQUsscUJBQWtCLENBQUM7QUFDcEMsU0FBTztBQUNUOzs7QURqQkEsT0FBT0MsV0FBVTtBQUpqQixJQUFNQyxvQ0FBbUM7QUFLekMsSUFBTyxzQkFBUSxhQUFhLENBQUMsRUFBRSxNQUFNLFFBQVEsTUFBNkI7QUFDeEUsUUFBTSxNQUFNLFFBQVEsTUFBTSxRQUFRLElBQUksQ0FBQztBQUN2QyxTQUFPO0FBQUE7QUFBQTtBQUFBO0FBQUEsSUFJTCxNQUFNLElBQUk7QUFBQSxJQUNWLFNBQVM7QUFBQSxNQUNQLE9BQU87QUFBQSxRQUNMLEtBQUtDLE1BQUssUUFBUUMsbUNBQVcsSUFBSTtBQUFBLFFBQ2pDLEtBQUtELE1BQUssUUFBUUMsbUNBQVcsT0FBTztBQUFBLE1BQ3RDO0FBQUEsTUFDQSxZQUFZLENBQUMsUUFBUSxPQUFPLE9BQU8sUUFBUSxRQUFRLFNBQVMsUUFBUSxNQUFNO0FBQUEsSUFDNUU7QUFBQTtBQUFBLElBRUEsU0FBUyxnQkFBYyxLQUFLLFlBQVksT0FBTztBQUFBLElBQy9DLFFBQVE7QUFBQSxNQUNOLE1BQU07QUFBQSxNQUNOLE1BQU0sT0FBTyxJQUFJLGFBQWE7QUFBQSxNQUM5QixNQUFNO0FBQUEsTUFDTixPQUFPO0FBQUEsUUFDTCxDQUFDLElBQUksaUJBQWlCLEdBQUc7QUFBQSxVQUN2QixRQUFRO0FBQUEsVUFDUixjQUFjO0FBQUEsVUFDZCxJQUFJO0FBQUEsVUFDSixTQUFTLENBQUNELFVBQVNBLE1BQUssUUFBUSxJQUFJLE9BQU8sTUFBTSxJQUFJLGlCQUFpQixHQUFHLEVBQUU7QUFBQSxRQUM3RTtBQUFBLFFBQ0EsaUJBQWlCO0FBQUEsVUFDZixRQUFRLElBQUk7QUFBQSxVQUNaLGNBQWM7QUFBQSxVQUNkLElBQUk7QUFBQSxVQUNKLFNBQVMsQ0FBQ0EsVUFBU0EsTUFBSyxRQUFRLElBQUksT0FBTyxNQUFNLElBQUksaUJBQWlCLEdBQUcsRUFBRTtBQUFBLFFBQzdFO0FBQUEsUUFDQSxjQUFjO0FBQUEsVUFDWixRQUFRLElBQUk7QUFBQSxVQUNaLGNBQWM7QUFBQSxVQUNkLElBQUk7QUFBQSxVQUNKLFNBQVMsQ0FBQ0EsVUFBU0EsTUFBSyxRQUFRLElBQUksT0FBTyxNQUFNLElBQUksaUJBQWlCLEdBQUcsRUFBRTtBQUFBLFFBQzdFO0FBQUEsUUFDQSxhQUFhO0FBQUEsVUFDWCxRQUFRLElBQUk7QUFBQSxVQUNaLGNBQWM7QUFBQSxVQUNkLElBQUk7QUFBQSxVQUNKLFNBQVMsQ0FBQ0EsVUFBU0EsTUFBSyxRQUFRLElBQUksT0FBTyxNQUFNLElBQUksaUJBQWlCLEdBQUcsRUFBRTtBQUFBLFFBQzdFO0FBQUEsUUFDQyxTQUFTO0FBQUEsVUFDUixRQUFRLElBQUk7QUFBQSxVQUNaLGNBQWM7QUFBQSxVQUNkLElBQUk7QUFBQSxVQUNKLFNBQVMsQ0FBQ0EsVUFBU0EsTUFBSyxRQUFRLElBQUksT0FBTyxNQUFNLElBQUksaUJBQWlCLEdBQUcsRUFBRTtBQUFBLFFBQzdFO0FBQUEsTUFDRjtBQUFBLElBQ0Y7QUFBQSxJQUNBLEtBQUs7QUFBQSxNQUNILHFCQUFxQjtBQUFBLFFBQ25CLE1BQU07QUFBQSxVQUNKLG1CQUFtQjtBQUFBLFFBQ3JCO0FBQUEsTUFDRjtBQUFBLE1BQ0EsU0FBUztBQUFBLFFBQ1AsU0FBUztBQUFBLFVBQ1A7QUFBQSxZQUNFLGVBQWU7QUFBQSxZQUNmLFFBQVE7QUFBQSxjQUNOLFNBQVMsQ0FBQyxXQUFXO0FBQ25CLG9CQUFJLE9BQU8sU0FBUyxXQUFXO0FBQzdCLHlCQUFPLE9BQU87QUFBQSxnQkFDaEI7QUFBQSxjQUNGO0FBQUEsWUFDRjtBQUFBLFVBQ0Y7QUFBQSxRQUNGO0FBQUEsTUFDRjtBQUFBLElBQ0Y7QUFBQTtBQUFBLElBRUEsY0FBYztBQUFBLE1BQ1osU0FBUztBQUFBLFFBQ1A7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxNQUNGO0FBQUEsSUFDRjtBQUFBLEVBQ0Y7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogWyJwYXRoIiwgIkVsZW1lbnRQbHVzUmVzb2x2ZXIiLCAiSWNvbnNSZXNvbHZlciIsICJfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSIsICJwYXRoIiwgIkVsZW1lbnRQbHVzUmVzb2x2ZXIiLCAiSWNvbnNSZXNvbHZlciIsICJfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSIsICJfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSIsICJwYXRoIiwgIl9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lIiwgInBhdGgiLCAiX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUiLCAicGF0aCIsICJfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSJdCn0K diff --git a/vite.config.ts.timestamp-1756496937860-efea843310722.mjs b/vite.config.ts.timestamp-1756496937860-efea843310722.mjs deleted file mode 100644 index 4d6b455..0000000 --- a/vite.config.ts.timestamp-1756496937860-efea843310722.mjs +++ /dev/null @@ -1,230 +0,0 @@ -// vite.config.ts -import { loadEnv, defineConfig } from "file:///E:/ljj/plus-ui/node_modules/vite/dist/node/index.js"; - -// vite/plugins/index.ts -import vue from "file:///E:/ljj/plus-ui/node_modules/@vitejs/plugin-vue/dist/index.mjs"; - -// vite/plugins/unocss.ts -import UnoCss from "file:///E:/ljj/plus-ui/node_modules/unocss/dist/vite.mjs"; -var unocss_default = () => { - return UnoCss({ - hmrTopLevelAwait: false - // unocss默认是true,低版本浏览器是不支持的,启动后会报错 - }); -}; - -// vite/plugins/auto-import.ts -import AutoImport from "file:///E:/ljj/plus-ui/node_modules/unplugin-auto-import/dist/vite.js"; -import { ElementPlusResolver } from "file:///E:/ljj/plus-ui/node_modules/unplugin-vue-components/dist/resolvers.js"; -import IconsResolver from "file:///E:/ljj/plus-ui/node_modules/unplugin-icons/dist/resolver.js"; -var __vite_injected_original_dirname = "E:\\ljj\\plus-ui\\vite\\plugins"; -var auto_import_default = (path3) => { - return AutoImport({ - // 自动导入 Vue 相关函数 - imports: ["vue", "vue-router", "@vueuse/core", "pinia"], - eslintrc: { - enabled: false, - filepath: "./.eslintrc-auto-import.json", - globalsPropValue: true - }, - resolvers: [ - // 自动导入 Element Plus 相关函数ElMessage, ElMessageBox... (带样式) - ElementPlusResolver(), - IconsResolver({ - prefix: "Icon" - }) - ], - vueTemplate: true, - // 是否在 vue 模板中自动导入 - dts: path3.resolve(path3.resolve(__vite_injected_original_dirname, "../../src"), "types", "auto-imports.d.ts") - }); -}; - -// vite/plugins/components.ts -import Components from "file:///E:/ljj/plus-ui/node_modules/unplugin-vue-components/dist/vite.js"; -import { ElementPlusResolver as ElementPlusResolver2 } from "file:///E:/ljj/plus-ui/node_modules/unplugin-vue-components/dist/resolvers.js"; -import IconsResolver2 from "file:///E:/ljj/plus-ui/node_modules/unplugin-icons/dist/resolver.js"; -var __vite_injected_original_dirname2 = "E:\\ljj\\plus-ui\\vite\\plugins"; -var components_default = (path3) => { - return Components({ - resolvers: [ - // 自动导入 Element Plus 组件 - ElementPlusResolver2(), - // 自动注册图标组件 - IconsResolver2({ - enabledCollections: ["ep"] - }) - ], - dts: path3.resolve(path3.resolve(__vite_injected_original_dirname2, "../../src"), "types", "components.d.ts") - }); -}; - -// vite/plugins/icons.ts -import Icons from "file:///E:/ljj/plus-ui/node_modules/unplugin-icons/dist/vite.js"; -var icons_default = () => { - return Icons({ - // 自动安装图标库 - autoInstall: true - }); -}; - -// vite/plugins/svg-icon.ts -import { createSvgIconsPlugin } from "file:///E:/ljj/plus-ui/node_modules/vite-plugin-svg-icons/dist/index.mjs"; -var __vite_injected_original_dirname3 = "E:\\ljj\\plus-ui\\vite\\plugins"; -var svg_icon_default = (path3, isBuild) => { - return createSvgIconsPlugin({ - // 指定需要缓存的图标文件夹 - iconDirs: [path3.resolve(path3.resolve(__vite_injected_original_dirname3, "../../src"), "assets/icons/svg")], - // 指定symbolId格式 - symbolId: "icon-[dir]-[name]", - svgoOptions: isBuild - }); -}; - -// vite/plugins/compression.ts -import compression from "file:///E:/ljj/plus-ui/node_modules/vite-plugin-compression/dist/index.mjs"; -var compression_default = (env) => { - const { VITE_BUILD_COMPRESS } = env; - const plugin = []; - if (VITE_BUILD_COMPRESS) { - const compressList = VITE_BUILD_COMPRESS.split(","); - if (compressList.includes("gzip")) { - plugin.push( - compression({ - ext: ".gz", - deleteOriginFile: false - }) - ); - } - if (compressList.includes("brotli")) { - plugin.push( - compression({ - ext: ".br", - algorithm: "brotliCompress", - deleteOriginFile: false - }) - ); - } - } - return plugin; -}; - -// vite/plugins/setup-extend.ts -import setupExtend from "file:///E:/ljj/plus-ui/node_modules/unplugin-vue-setup-extend-plus/dist/vite.js"; -var setup_extend_default = () => { - return setupExtend({}); -}; - -// vite/plugins/index.ts -import path from "path"; -var plugins_default = (viteEnv, isBuild = false) => { - const vitePlugins = []; - vitePlugins.push(vue()); - vitePlugins.push(unocss_default()); - vitePlugins.push(auto_import_default(path)); - vitePlugins.push(components_default(path)); - vitePlugins.push(compression_default(viteEnv)); - vitePlugins.push(icons_default()); - vitePlugins.push(svg_icon_default(path, isBuild)); - vitePlugins.push(setup_extend_default()); - return vitePlugins; -}; - -// vite.config.ts -import path2 from "path"; -var __vite_injected_original_dirname4 = "E:\\ljj\\plus-ui"; -var vite_config_default = defineConfig(({ mode, command }) => { - const env = loadEnv(mode, process.cwd()); - return { - // 部署生产环境和开发环境下的URL。 - // 默认情况下,vite 会假设你的应用是被部署在一个域名的根路径上 - // 例如 https://www.ruoyi.vip/。如果应用被部署在一个子路径上,你就需要用这个选项指定这个子路径。例如,如果你的应用被部署在 https://www.ruoyi.vip/admin/,则设置 baseUrl 为 /admin/。 - base: env.VITE_APP_CONTEXT_PATH, - resolve: { - alias: { - "~": path2.resolve(__vite_injected_original_dirname4, "./"), - "@": path2.resolve(__vite_injected_original_dirname4, "./src") - }, - extensions: [".mjs", ".js", ".ts", ".jsx", ".tsx", ".json", ".vue", ".tif"] - }, - // https://cn.vitejs.dev/config/#resolve-extensions - plugins: plugins_default(env, command === "build"), - server: { - host: "0.0.0.0", - port: Number(env.VITE_APP_PORT), - open: true, - proxy: { - [env.VITE_APP_BASE_API]: { - target: "http://localhost:8899", - changeOrigin: true, - ws: true, - rewrite: (path3) => path3.replace(new RegExp("^" + env.VITE_APP_BASE_API), "") - }, - "/warm-flow-ui": { - target: env.VITE_APP_BASE_API, - changeOrigin: true, - ws: true, - rewrite: (path3) => path3.replace(new RegExp("^" + env.VITE_APP_BASE_API), "") - }, - "/warm-flow": { - target: env.VITE_APP_BASE_API, - changeOrigin: true, - ws: true, - rewrite: (path3) => path3.replace(new RegExp("^" + env.VITE_APP_BASE_API), "") - }, - "/workflow": { - target: env.VITE_APP_BASE_API, - changeOrigin: true, - ws: true, - rewrite: (path3) => path3.replace(new RegExp("^" + env.VITE_APP_BASE_API), "") - }, - "/auth": { - target: env.VITE_APP_BASE_API, - changeOrigin: true, - ws: true, - rewrite: (path3) => path3.replace(new RegExp("^" + env.VITE_APP_BASE_API), "") - } - } - }, - css: { - preprocessorOptions: { - scss: { - javascriptEnabled: true - } - }, - postcss: { - plugins: [ - { - postcssPlugin: "internal:charset-removal", - AtRule: { - charset: (atRule) => { - if (atRule.name === "charset") { - atRule.remove(); - } - } - } - } - ] - } - }, - // 预编译 - optimizeDeps: { - include: [ - "vue", - "vue-router", - "pinia", - "axios", - "@vueuse/core", - "echarts", - "vue-i18n", - "@vueup/vue-quill", - "image-conversion", - "element-plus/es/components/**/css" - ] - } - }; -}); -export { - vite_config_default as default -}; -//# sourceMappingURL=data:application/json;base64,ewogICJ2ZXJzaW9uIjogMywKICAic291cmNlcyI6IFsidml0ZS5jb25maWcudHMiLCAidml0ZS9wbHVnaW5zL2luZGV4LnRzIiwgInZpdGUvcGx1Z2lucy91bm9jc3MudHMiLCAidml0ZS9wbHVnaW5zL2F1dG8taW1wb3J0LnRzIiwgInZpdGUvcGx1Z2lucy9jb21wb25lbnRzLnRzIiwgInZpdGUvcGx1Z2lucy9pY29ucy50cyIsICJ2aXRlL3BsdWdpbnMvc3ZnLWljb24udHMiLCAidml0ZS9wbHVnaW5zL2NvbXByZXNzaW9uLnRzIiwgInZpdGUvcGx1Z2lucy9zZXR1cC1leHRlbmQudHMiXSwKICAic291cmNlc0NvbnRlbnQiOiBbImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJFOlxcXFxsampcXFxccGx1cy11aVwiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiRTpcXFxcbGpqXFxcXHBsdXMtdWlcXFxcdml0ZS5jb25maWcudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL0U6L2xqai9wbHVzLXVpL3ZpdGUuY29uZmlnLnRzXCI7aW1wb3J0IHsgVXNlckNvbmZpZywgQ29uZmlnRW52LCBsb2FkRW52LCBkZWZpbmVDb25maWcgfSBmcm9tICd2aXRlJztcclxuXHJcbmltcG9ydCBjcmVhdGVQbHVnaW5zIGZyb20gJy4vdml0ZS9wbHVnaW5zJztcclxuXHJcbmltcG9ydCBwYXRoIGZyb20gJ3BhdGgnO1xyXG5leHBvcnQgZGVmYXVsdCBkZWZpbmVDb25maWcoKHsgbW9kZSwgY29tbWFuZCB9OiBDb25maWdFbnYpOiBVc2VyQ29uZmlnID0+IHtcclxuICBjb25zdCBlbnYgPSBsb2FkRW52KG1vZGUsIHByb2Nlc3MuY3dkKCkpO1xyXG4gIHJldHVybiB7XHJcbiAgICAvLyBcdTkwRThcdTdGNzJcdTc1MUZcdTRFQTdcdTczQUZcdTU4ODNcdTU0OENcdTVGMDBcdTUzRDFcdTczQUZcdTU4ODNcdTRFMEJcdTc2ODRVUkxcdTMwMDJcclxuICAgIC8vIFx1OUVEOFx1OEJBNFx1NjBDNVx1NTFCNVx1NEUwQlx1RkYwQ3ZpdGUgXHU0RjFBXHU1MDQ3XHU4QkJFXHU0RjYwXHU3Njg0XHU1RTk0XHU3NTI4XHU2NjJGXHU4OEFCXHU5MEU4XHU3RjcyXHU1NzI4XHU0RTAwXHU0RTJBXHU1N0RGXHU1NDBEXHU3Njg0XHU2ODM5XHU4REVGXHU1Rjg0XHU0RTBBXHJcbiAgICAvLyBcdTRGOEJcdTU5ODIgaHR0cHM6Ly93d3cucnVveWkudmlwL1x1MzAwMlx1NTk4Mlx1Njc5Q1x1NUU5NFx1NzUyOFx1ODhBQlx1OTBFOFx1N0Y3Mlx1NTcyOFx1NEUwMFx1NEUyQVx1NUI1MFx1OERFRlx1NUY4NFx1NEUwQVx1RkYwQ1x1NEY2MFx1NUMzMVx1OTcwMFx1ODk4MVx1NzUyOFx1OEZEOVx1NEUyQVx1OTAwOVx1OTg3OVx1NjMwN1x1NUI5QVx1OEZEOVx1NEUyQVx1NUI1MFx1OERFRlx1NUY4NFx1MzAwMlx1NEY4Qlx1NTk4Mlx1RkYwQ1x1NTk4Mlx1Njc5Q1x1NEY2MFx1NzY4NFx1NUU5NFx1NzUyOFx1ODhBQlx1OTBFOFx1N0Y3Mlx1NTcyOCBodHRwczovL3d3dy5ydW95aS52aXAvYWRtaW4vXHVGRjBDXHU1MjE5XHU4QkJFXHU3RjZFIGJhc2VVcmwgXHU0RTNBIC9hZG1pbi9cdTMwMDJcclxuICAgIGJhc2U6IGVudi5WSVRFX0FQUF9DT05URVhUX1BBVEgsXHJcbiAgICByZXNvbHZlOiB7XHJcbiAgICAgIGFsaWFzOiB7XHJcbiAgICAgICAgJ34nOiBwYXRoLnJlc29sdmUoX19kaXJuYW1lLCAnLi8nKSxcclxuICAgICAgICAnQCc6IHBhdGgucmVzb2x2ZShfX2Rpcm5hbWUsICcuL3NyYycpXHJcbiAgICAgIH0sXHJcbiAgICAgIGV4dGVuc2lvbnM6IFsnLm1qcycsICcuanMnLCAnLnRzJywgJy5qc3gnLCAnLnRzeCcsICcuanNvbicsICcudnVlJywgJy50aWYnXVxyXG4gICAgfSxcclxuICAgIC8vIGh0dHBzOi8vY24udml0ZWpzLmRldi9jb25maWcvI3Jlc29sdmUtZXh0ZW5zaW9uc1xyXG4gICAgcGx1Z2luczogY3JlYXRlUGx1Z2lucyhlbnYsIGNvbW1hbmQgPT09ICdidWlsZCcpLFxyXG4gICAgc2VydmVyOiB7XHJcbiAgICAgIGhvc3Q6ICcwLjAuMC4wJyxcclxuICAgICAgcG9ydDogTnVtYmVyKGVudi5WSVRFX0FQUF9QT1JUKSxcclxuICAgICAgb3BlbjogdHJ1ZSxcclxuICAgICAgcHJveHk6IHtcclxuICAgICAgICBbZW52LlZJVEVfQVBQX0JBU0VfQVBJXToge1xyXG4gICAgICAgICAgdGFyZ2V0OiAnaHR0cDovL2xvY2FsaG9zdDo4ODk5JyxcclxuICAgICAgICAgIGNoYW5nZU9yaWdpbjogdHJ1ZSxcclxuICAgICAgICAgIHdzOiB0cnVlLFxyXG4gICAgICAgICAgcmV3cml0ZTogKHBhdGgpID0+IHBhdGgucmVwbGFjZShuZXcgUmVnRXhwKCdeJyArIGVudi5WSVRFX0FQUF9CQVNFX0FQSSksICcnKVxyXG4gICAgICAgIH0sXHJcbiAgICAgICAgJy93YXJtLWZsb3ctdWknOiB7XHJcbiAgICAgICAgICB0YXJnZXQ6IGVudi5WSVRFX0FQUF9CQVNFX0FQSSxcclxuICAgICAgICAgIGNoYW5nZU9yaWdpbjogdHJ1ZSxcclxuICAgICAgICAgIHdzOiB0cnVlLFxyXG4gICAgICAgICAgcmV3cml0ZTogKHBhdGgpID0+IHBhdGgucmVwbGFjZShuZXcgUmVnRXhwKCdeJyArIGVudi5WSVRFX0FQUF9CQVNFX0FQSSksICcnKVxyXG4gICAgICAgIH0sXHJcbiAgICAgICAgJy93YXJtLWZsb3cnOiB7XHJcbiAgICAgICAgICB0YXJnZXQ6IGVudi5WSVRFX0FQUF9CQVNFX0FQSSxcclxuICAgICAgICAgIGNoYW5nZU9yaWdpbjogdHJ1ZSxcclxuICAgICAgICAgIHdzOiB0cnVlLFxyXG4gICAgICAgICAgcmV3cml0ZTogKHBhdGgpID0+IHBhdGgucmVwbGFjZShuZXcgUmVnRXhwKCdeJyArIGVudi5WSVRFX0FQUF9CQVNFX0FQSSksICcnKVxyXG4gICAgICAgIH0sXHJcbiAgICAgICAgJy93b3JrZmxvdyc6IHtcclxuICAgICAgICAgIHRhcmdldDogZW52LlZJVEVfQVBQX0JBU0VfQVBJLFxyXG4gICAgICAgICAgY2hhbmdlT3JpZ2luOiB0cnVlLFxyXG4gICAgICAgICAgd3M6IHRydWUsXHJcbiAgICAgICAgICByZXdyaXRlOiAocGF0aCkgPT4gcGF0aC5yZXBsYWNlKG5ldyBSZWdFeHAoJ14nICsgZW52LlZJVEVfQVBQX0JBU0VfQVBJKSwgJycpXHJcbiAgICAgICAgfSxcclxuICAgICAgICAgJy9hdXRoJzoge1xyXG4gICAgICAgICAgdGFyZ2V0OiBlbnYuVklURV9BUFBfQkFTRV9BUEksXHJcbiAgICAgICAgICBjaGFuZ2VPcmlnaW46IHRydWUsXHJcbiAgICAgICAgICB3czogdHJ1ZSxcclxuICAgICAgICAgIHJld3JpdGU6IChwYXRoKSA9PiBwYXRoLnJlcGxhY2UobmV3IFJlZ0V4cCgnXicgKyBlbnYuVklURV9BUFBfQkFTRV9BUEkpLCAnJylcclxuICAgICAgICB9XHJcbiAgICAgIH1cclxuICAgIH0sXHJcbiAgICBjc3M6IHtcclxuICAgICAgcHJlcHJvY2Vzc29yT3B0aW9uczoge1xyXG4gICAgICAgIHNjc3M6IHtcclxuICAgICAgICAgIGphdmFzY3JpcHRFbmFibGVkOiB0cnVlXHJcbiAgICAgICAgfVxyXG4gICAgICB9LFxyXG4gICAgICBwb3N0Y3NzOiB7XHJcbiAgICAgICAgcGx1Z2luczogW1xyXG4gICAgICAgICAge1xyXG4gICAgICAgICAgICBwb3N0Y3NzUGx1Z2luOiAnaW50ZXJuYWw6Y2hhcnNldC1yZW1vdmFsJyxcclxuICAgICAgICAgICAgQXRSdWxlOiB7XHJcbiAgICAgICAgICAgICAgY2hhcnNldDogKGF0UnVsZSkgPT4ge1xyXG4gICAgICAgICAgICAgICAgaWYgKGF0UnVsZS5uYW1lID09PSAnY2hhcnNldCcpIHtcclxuICAgICAgICAgICAgICAgICAgYXRSdWxlLnJlbW92ZSgpO1xyXG4gICAgICAgICAgICAgICAgfVxyXG4gICAgICAgICAgICAgIH1cclxuICAgICAgICAgICAgfVxyXG4gICAgICAgICAgfVxyXG4gICAgICAgIF1cclxuICAgICAgfVxyXG4gICAgfSxcclxuICAgIC8vIFx1OTg4NFx1N0YxNlx1OEJEMVxyXG4gICAgb3B0aW1pemVEZXBzOiB7XHJcbiAgICAgIGluY2x1ZGU6IFtcclxuICAgICAgICAndnVlJyxcclxuICAgICAgICAndnVlLXJvdXRlcicsXHJcbiAgICAgICAgJ3BpbmlhJyxcclxuICAgICAgICAnYXhpb3MnLFxyXG4gICAgICAgICdAdnVldXNlL2NvcmUnLFxyXG4gICAgICAgICdlY2hhcnRzJyxcclxuICAgICAgICAndnVlLWkxOG4nLFxyXG4gICAgICAgICdAdnVldXAvdnVlLXF1aWxsJyxcclxuICAgICAgICAnaW1hZ2UtY29udmVyc2lvbicsXHJcbiAgICAgICAgJ2VsZW1lbnQtcGx1cy9lcy9jb21wb25lbnRzLyoqL2NzcydcclxuICAgICAgXVxyXG4gICAgfVxyXG4gIH07XHJcbn0pO1xyXG4iLCAiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIkU6XFxcXGxqalxcXFxwbHVzLXVpXFxcXHZpdGVcXFxccGx1Z2luc1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiRTpcXFxcbGpqXFxcXHBsdXMtdWlcXFxcdml0ZVxcXFxwbHVnaW5zXFxcXGluZGV4LnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9FOi9samovcGx1cy11aS92aXRlL3BsdWdpbnMvaW5kZXgudHNcIjtpbXBvcnQgdnVlIGZyb20gJ0B2aXRlanMvcGx1Z2luLXZ1ZSc7XHJcbmltcG9ydCBjcmVhdGVVbm9Dc3MgZnJvbSAnLi91bm9jc3MnO1xyXG5pbXBvcnQgY3JlYXRlQXV0b0ltcG9ydCBmcm9tICcuL2F1dG8taW1wb3J0JztcclxuaW1wb3J0IGNyZWF0ZUNvbXBvbmVudHMgZnJvbSAnLi9jb21wb25lbnRzJztcclxuaW1wb3J0IGNyZWF0ZUljb25zIGZyb20gJy4vaWNvbnMnO1xyXG5pbXBvcnQgY3JlYXRlU3ZnSWNvbnNQbHVnaW4gZnJvbSAnLi9zdmctaWNvbic7XHJcbmltcG9ydCBjcmVhdGVDb21wcmVzc2lvbiBmcm9tICcuL2NvbXByZXNzaW9uJztcclxuaW1wb3J0IGNyZWF0ZVNldHVwRXh0ZW5kIGZyb20gJy4vc2V0dXAtZXh0ZW5kJztcclxuaW1wb3J0IHBhdGggZnJvbSAncGF0aCc7XHJcblxyXG5leHBvcnQgZGVmYXVsdCAodml0ZUVudjogYW55LCBpc0J1aWxkID0gZmFsc2UpOiBbXSA9PiB7XHJcbiAgY29uc3Qgdml0ZVBsdWdpbnM6IGFueSA9IFtdO1xyXG4gIHZpdGVQbHVnaW5zLnB1c2godnVlKCkpO1xyXG4gIHZpdGVQbHVnaW5zLnB1c2goY3JlYXRlVW5vQ3NzKCkpO1xyXG4gIHZpdGVQbHVnaW5zLnB1c2goY3JlYXRlQXV0b0ltcG9ydChwYXRoKSk7XHJcbiAgdml0ZVBsdWdpbnMucHVzaChjcmVhdGVDb21wb25lbnRzKHBhdGgpKTtcclxuICB2aXRlUGx1Z2lucy5wdXNoKGNyZWF0ZUNvbXByZXNzaW9uKHZpdGVFbnYpKTtcclxuICB2aXRlUGx1Z2lucy5wdXNoKGNyZWF0ZUljb25zKCkpO1xyXG4gIHZpdGVQbHVnaW5zLnB1c2goY3JlYXRlU3ZnSWNvbnNQbHVnaW4ocGF0aCwgaXNCdWlsZCkpO1xyXG4gIHZpdGVQbHVnaW5zLnB1c2goY3JlYXRlU2V0dXBFeHRlbmQoKSk7XHJcbiAgcmV0dXJuIHZpdGVQbHVnaW5zO1xyXG59O1xyXG4iLCAiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIkU6XFxcXGxqalxcXFxwbHVzLXVpXFxcXHZpdGVcXFxccGx1Z2luc1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiRTpcXFxcbGpqXFxcXHBsdXMtdWlcXFxcdml0ZVxcXFxwbHVnaW5zXFxcXHVub2Nzcy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vRTovbGpqL3BsdXMtdWkvdml0ZS9wbHVnaW5zL3Vub2Nzcy50c1wiO2ltcG9ydCBVbm9Dc3MgZnJvbSAndW5vY3NzL3ZpdGUnO1xyXG5cclxuZXhwb3J0IGRlZmF1bHQgKCkgPT4ge1xyXG4gIHJldHVybiBVbm9Dc3Moe1xyXG4gICAgaG1yVG9wTGV2ZWxBd2FpdDogZmFsc2UgLy8gdW5vY3NzXHU5RUQ4XHU4QkE0XHU2NjJGdHJ1ZVx1RkYwQ1x1NEY0RVx1NzI0OFx1NjcyQ1x1NkQ0Rlx1ODlDOFx1NTY2OFx1NjYyRlx1NEUwRFx1NjUyRlx1NjMwMVx1NzY4NFx1RkYwQ1x1NTQyRlx1NTJBOFx1NTQwRVx1NEYxQVx1NjJBNVx1OTUxOVxyXG4gIH0pO1xyXG59O1xyXG4iLCAiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIkU6XFxcXGxqalxcXFxwbHVzLXVpXFxcXHZpdGVcXFxccGx1Z2luc1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiRTpcXFxcbGpqXFxcXHBsdXMtdWlcXFxcdml0ZVxcXFxwbHVnaW5zXFxcXGF1dG8taW1wb3J0LnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9FOi9samovcGx1cy11aS92aXRlL3BsdWdpbnMvYXV0by1pbXBvcnQudHNcIjtpbXBvcnQgQXV0b0ltcG9ydCBmcm9tICd1bnBsdWdpbi1hdXRvLWltcG9ydC92aXRlJztcclxuaW1wb3J0IHsgRWxlbWVudFBsdXNSZXNvbHZlciB9IGZyb20gJ3VucGx1Z2luLXZ1ZS1jb21wb25lbnRzL3Jlc29sdmVycyc7XHJcbmltcG9ydCBJY29uc1Jlc29sdmVyIGZyb20gJ3VucGx1Z2luLWljb25zL3Jlc29sdmVyJztcclxuXHJcbmV4cG9ydCBkZWZhdWx0IChwYXRoOiBhbnkpID0+IHtcclxuICByZXR1cm4gQXV0b0ltcG9ydCh7XHJcbiAgICAvLyBcdTgxRUFcdTUyQThcdTVCRkNcdTUxNjUgVnVlIFx1NzZGOFx1NTE3M1x1NTFGRFx1NjU3MFxyXG4gICAgaW1wb3J0czogWyd2dWUnLCAndnVlLXJvdXRlcicsICdAdnVldXNlL2NvcmUnLCAncGluaWEnXSxcclxuICAgIGVzbGludHJjOiB7XHJcbiAgICAgIGVuYWJsZWQ6IGZhbHNlLFxyXG4gICAgICBmaWxlcGF0aDogJy4vLmVzbGludHJjLWF1dG8taW1wb3J0Lmpzb24nLFxyXG4gICAgICBnbG9iYWxzUHJvcFZhbHVlOiB0cnVlXHJcbiAgICB9LFxyXG4gICAgcmVzb2x2ZXJzOiBbXHJcbiAgICAgIC8vIFx1ODFFQVx1NTJBOFx1NUJGQ1x1NTE2NSBFbGVtZW50IFBsdXMgXHU3NkY4XHU1MTczXHU1MUZEXHU2NTcwRWxNZXNzYWdlLCBFbE1lc3NhZ2VCb3guLi4gKFx1NUUyNlx1NjgzN1x1NUYwRilcclxuICAgICAgRWxlbWVudFBsdXNSZXNvbHZlcigpLFxyXG4gICAgICBJY29uc1Jlc29sdmVyKHtcclxuICAgICAgICBwcmVmaXg6ICdJY29uJ1xyXG4gICAgICB9KVxyXG4gICAgXSxcclxuICAgIHZ1ZVRlbXBsYXRlOiB0cnVlLCAvLyBcdTY2MkZcdTU0MjZcdTU3MjggdnVlIFx1NkEyMVx1Njc3Rlx1NEUyRFx1ODFFQVx1NTJBOFx1NUJGQ1x1NTE2NVxyXG4gICAgZHRzOiBwYXRoLnJlc29sdmUocGF0aC5yZXNvbHZlKF9fZGlybmFtZSwgJy4uLy4uL3NyYycpLCAndHlwZXMnLCAnYXV0by1pbXBvcnRzLmQudHMnKVxyXG4gIH0pO1xyXG59O1xyXG4iLCAiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIkU6XFxcXGxqalxcXFxwbHVzLXVpXFxcXHZpdGVcXFxccGx1Z2luc1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiRTpcXFxcbGpqXFxcXHBsdXMtdWlcXFxcdml0ZVxcXFxwbHVnaW5zXFxcXGNvbXBvbmVudHMudHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL0U6L2xqai9wbHVzLXVpL3ZpdGUvcGx1Z2lucy9jb21wb25lbnRzLnRzXCI7aW1wb3J0IENvbXBvbmVudHMgZnJvbSAndW5wbHVnaW4tdnVlLWNvbXBvbmVudHMvdml0ZSc7XHJcbmltcG9ydCB7IEVsZW1lbnRQbHVzUmVzb2x2ZXIgfSBmcm9tICd1bnBsdWdpbi12dWUtY29tcG9uZW50cy9yZXNvbHZlcnMnO1xyXG5pbXBvcnQgSWNvbnNSZXNvbHZlciBmcm9tICd1bnBsdWdpbi1pY29ucy9yZXNvbHZlcic7XHJcblxyXG5leHBvcnQgZGVmYXVsdCAocGF0aDogYW55KSA9PiB7XHJcbiAgcmV0dXJuIENvbXBvbmVudHMoe1xyXG4gICAgcmVzb2x2ZXJzOiBbXHJcbiAgICAgIC8vIFx1ODFFQVx1NTJBOFx1NUJGQ1x1NTE2NSBFbGVtZW50IFBsdXMgXHU3RUM0XHU0RUY2XHJcbiAgICAgIEVsZW1lbnRQbHVzUmVzb2x2ZXIoKSxcclxuICAgICAgLy8gXHU4MUVBXHU1MkE4XHU2Q0U4XHU1MThDXHU1NkZFXHU2ODA3XHU3RUM0XHU0RUY2XHJcbiAgICAgIEljb25zUmVzb2x2ZXIoe1xyXG4gICAgICAgIGVuYWJsZWRDb2xsZWN0aW9uczogWydlcCddXHJcbiAgICAgIH0pXHJcbiAgICBdLFxyXG4gICAgZHRzOiBwYXRoLnJlc29sdmUocGF0aC5yZXNvbHZlKF9fZGlybmFtZSwgJy4uLy4uL3NyYycpLCAndHlwZXMnLCAnY29tcG9uZW50cy5kLnRzJylcclxuICB9KTtcclxufTtcclxuIiwgImNvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lID0gXCJFOlxcXFxsampcXFxccGx1cy11aVxcXFx2aXRlXFxcXHBsdWdpbnNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZmlsZW5hbWUgPSBcIkU6XFxcXGxqalxcXFxwbHVzLXVpXFxcXHZpdGVcXFxccGx1Z2luc1xcXFxpY29ucy50c1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9pbXBvcnRfbWV0YV91cmwgPSBcImZpbGU6Ly8vRTovbGpqL3BsdXMtdWkvdml0ZS9wbHVnaW5zL2ljb25zLnRzXCI7aW1wb3J0IEljb25zIGZyb20gJ3VucGx1Z2luLWljb25zL3ZpdGUnO1xyXG5cclxuZXhwb3J0IGRlZmF1bHQgKCkgPT4ge1xyXG4gIHJldHVybiBJY29ucyh7XHJcbiAgICAvLyBcdTgxRUFcdTUyQThcdTVCODlcdTg4QzVcdTU2RkVcdTY4MDdcdTVFOTNcclxuICAgIGF1dG9JbnN0YWxsOiB0cnVlXHJcbiAgfSk7XHJcbn07XHJcbiIsICJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiRTpcXFxcbGpqXFxcXHBsdXMtdWlcXFxcdml0ZVxcXFxwbHVnaW5zXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCJFOlxcXFxsampcXFxccGx1cy11aVxcXFx2aXRlXFxcXHBsdWdpbnNcXFxcc3ZnLWljb24udHNcIjtjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfaW1wb3J0X21ldGFfdXJsID0gXCJmaWxlOi8vL0U6L2xqai9wbHVzLXVpL3ZpdGUvcGx1Z2lucy9zdmctaWNvbi50c1wiO2ltcG9ydCB7IGNyZWF0ZVN2Z0ljb25zUGx1Z2luIH0gZnJvbSAndml0ZS1wbHVnaW4tc3ZnLWljb25zJztcclxuZXhwb3J0IGRlZmF1bHQgKHBhdGg6IGFueSwgaXNCdWlsZDogYm9vbGVhbikgPT4ge1xyXG4gIHJldHVybiBjcmVhdGVTdmdJY29uc1BsdWdpbih7XHJcbiAgICAvLyBcdTYzMDdcdTVCOUFcdTk3MDBcdTg5ODFcdTdGMTNcdTVCNThcdTc2ODRcdTU2RkVcdTY4MDdcdTY1ODdcdTRFRjZcdTU5MzlcclxuICAgIGljb25EaXJzOiBbcGF0aC5yZXNvbHZlKHBhdGgucmVzb2x2ZShfX2Rpcm5hbWUsICcuLi8uLi9zcmMnKSwgJ2Fzc2V0cy9pY29ucy9zdmcnKV0sXHJcbiAgICAvLyBcdTYzMDdcdTVCOUFzeW1ib2xJZFx1NjgzQ1x1NUYwRlxyXG4gICAgc3ltYm9sSWQ6ICdpY29uLVtkaXJdLVtuYW1lXScsXHJcbiAgICBzdmdvT3B0aW9uczogaXNCdWlsZFxyXG4gIH0pO1xyXG59O1xyXG4iLCAiY29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUgPSBcIkU6XFxcXGxqalxcXFxwbHVzLXVpXFxcXHZpdGVcXFxccGx1Z2luc1wiO2NvbnN0IF9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9maWxlbmFtZSA9IFwiRTpcXFxcbGpqXFxcXHBsdXMtdWlcXFxcdml0ZVxcXFxwbHVnaW5zXFxcXGNvbXByZXNzaW9uLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9FOi9samovcGx1cy11aS92aXRlL3BsdWdpbnMvY29tcHJlc3Npb24udHNcIjtpbXBvcnQgY29tcHJlc3Npb24gZnJvbSAndml0ZS1wbHVnaW4tY29tcHJlc3Npb24nO1xyXG5cclxuZXhwb3J0IGRlZmF1bHQgKGVudjogYW55KSA9PiB7XHJcbiAgY29uc3QgeyBWSVRFX0JVSUxEX0NPTVBSRVNTIH0gPSBlbnY7XHJcbiAgY29uc3QgcGx1Z2luOiBhbnlbXSA9IFtdO1xyXG4gIGlmIChWSVRFX0JVSUxEX0NPTVBSRVNTKSB7XHJcbiAgICBjb25zdCBjb21wcmVzc0xpc3QgPSBWSVRFX0JVSUxEX0NPTVBSRVNTLnNwbGl0KCcsJyk7XHJcbiAgICBpZiAoY29tcHJlc3NMaXN0LmluY2x1ZGVzKCdnemlwJykpIHtcclxuICAgICAgLy8gaHR0cDovL2RvYy5ydW95aS52aXAvcnVveWktdnVlL290aGVyL2ZhcS5odG1sI1x1NEY3Rlx1NzUyOGd6aXBcdTg5RTNcdTUzOEJcdTdGMjlcdTk3NTlcdTYwMDFcdTY1ODdcdTRFRjZcclxuICAgICAgcGx1Z2luLnB1c2goXHJcbiAgICAgICAgY29tcHJlc3Npb24oe1xyXG4gICAgICAgICAgZXh0OiAnLmd6JyxcclxuICAgICAgICAgIGRlbGV0ZU9yaWdpbkZpbGU6IGZhbHNlXHJcbiAgICAgICAgfSlcclxuICAgICAgKTtcclxuICAgIH1cclxuICAgIGlmIChjb21wcmVzc0xpc3QuaW5jbHVkZXMoJ2Jyb3RsaScpKSB7XHJcbiAgICAgIHBsdWdpbi5wdXNoKFxyXG4gICAgICAgIGNvbXByZXNzaW9uKHtcclxuICAgICAgICAgIGV4dDogJy5icicsXHJcbiAgICAgICAgICBhbGdvcml0aG06ICdicm90bGlDb21wcmVzcycsXHJcbiAgICAgICAgICBkZWxldGVPcmlnaW5GaWxlOiBmYWxzZVxyXG4gICAgICAgIH0pXHJcbiAgICAgICk7XHJcbiAgICB9XHJcbiAgfVxyXG4gIHJldHVybiBwbHVnaW47XHJcbn07XHJcbiIsICJjb25zdCBfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSA9IFwiRTpcXFxcbGpqXFxcXHBsdXMtdWlcXFxcdml0ZVxcXFxwbHVnaW5zXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ZpbGVuYW1lID0gXCJFOlxcXFxsampcXFxccGx1cy11aVxcXFx2aXRlXFxcXHBsdWdpbnNcXFxcc2V0dXAtZXh0ZW5kLnRzXCI7Y29uc3QgX192aXRlX2luamVjdGVkX29yaWdpbmFsX2ltcG9ydF9tZXRhX3VybCA9IFwiZmlsZTovLy9FOi9samovcGx1cy11aS92aXRlL3BsdWdpbnMvc2V0dXAtZXh0ZW5kLnRzXCI7aW1wb3J0IHNldHVwRXh0ZW5kIGZyb20gJ3VucGx1Z2luLXZ1ZS1zZXR1cC1leHRlbmQtcGx1cy92aXRlJztcclxuXHJcbmV4cG9ydCBkZWZhdWx0ICgpID0+IHtcclxuICByZXR1cm4gc2V0dXBFeHRlbmQoe30pO1xyXG59O1xyXG4iXSwKICAibWFwcGluZ3MiOiAiO0FBQWtPLFNBQWdDLFNBQVMsb0JBQW9COzs7QUNBOUIsT0FBTyxTQUFTOzs7QUNBZCxPQUFPLFlBQVk7QUFFdFIsSUFBTyxpQkFBUSxNQUFNO0FBQ25CLFNBQU8sT0FBTztBQUFBLElBQ1osa0JBQWtCO0FBQUE7QUFBQSxFQUNwQixDQUFDO0FBQ0g7OztBQ042USxPQUFPLGdCQUFnQjtBQUNwUyxTQUFTLDJCQUEyQjtBQUNwQyxPQUFPLG1CQUFtQjtBQUYxQixJQUFNLG1DQUFtQztBQUl6QyxJQUFPLHNCQUFRLENBQUNBLFVBQWM7QUFDNUIsU0FBTyxXQUFXO0FBQUE7QUFBQSxJQUVoQixTQUFTLENBQUMsT0FBTyxjQUFjLGdCQUFnQixPQUFPO0FBQUEsSUFDdEQsVUFBVTtBQUFBLE1BQ1IsU0FBUztBQUFBLE1BQ1QsVUFBVTtBQUFBLE1BQ1Ysa0JBQWtCO0FBQUEsSUFDcEI7QUFBQSxJQUNBLFdBQVc7QUFBQTtBQUFBLE1BRVQsb0JBQW9CO0FBQUEsTUFDcEIsY0FBYztBQUFBLFFBQ1osUUFBUTtBQUFBLE1BQ1YsQ0FBQztBQUFBLElBQ0g7QUFBQSxJQUNBLGFBQWE7QUFBQTtBQUFBLElBQ2IsS0FBS0EsTUFBSyxRQUFRQSxNQUFLLFFBQVEsa0NBQVcsV0FBVyxHQUFHLFNBQVMsbUJBQW1CO0FBQUEsRUFDdEYsQ0FBQztBQUNIOzs7QUN2QjJRLE9BQU8sZ0JBQWdCO0FBQ2xTLFNBQVMsdUJBQUFDLDRCQUEyQjtBQUNwQyxPQUFPQyxvQkFBbUI7QUFGMUIsSUFBTUMsb0NBQW1DO0FBSXpDLElBQU8scUJBQVEsQ0FBQ0MsVUFBYztBQUM1QixTQUFPLFdBQVc7QUFBQSxJQUNoQixXQUFXO0FBQUE7QUFBQSxNQUVUQyxxQkFBb0I7QUFBQTtBQUFBLE1BRXBCQyxlQUFjO0FBQUEsUUFDWixvQkFBb0IsQ0FBQyxJQUFJO0FBQUEsTUFDM0IsQ0FBQztBQUFBLElBQ0g7QUFBQSxJQUNBLEtBQUtGLE1BQUssUUFBUUEsTUFBSyxRQUFRRyxtQ0FBVyxXQUFXLEdBQUcsU0FBUyxpQkFBaUI7QUFBQSxFQUNwRixDQUFDO0FBQ0g7OztBQ2hCaVEsT0FBTyxXQUFXO0FBRW5SLElBQU8sZ0JBQVEsTUFBTTtBQUNuQixTQUFPLE1BQU07QUFBQTtBQUFBLElBRVgsYUFBYTtBQUFBLEVBQ2YsQ0FBQztBQUNIOzs7QUNQdVEsU0FBUyw0QkFBNEI7QUFBNVMsSUFBTUMsb0NBQW1DO0FBQ3pDLElBQU8sbUJBQVEsQ0FBQ0MsT0FBVyxZQUFxQjtBQUM5QyxTQUFPLHFCQUFxQjtBQUFBO0FBQUEsSUFFMUIsVUFBVSxDQUFDQSxNQUFLLFFBQVFBLE1BQUssUUFBUUMsbUNBQVcsV0FBVyxHQUFHLGtCQUFrQixDQUFDO0FBQUE7QUFBQSxJQUVqRixVQUFVO0FBQUEsSUFDVixhQUFhO0FBQUEsRUFDZixDQUFDO0FBQ0g7OztBQ1Q2USxPQUFPLGlCQUFpQjtBQUVyUyxJQUFPLHNCQUFRLENBQUMsUUFBYTtBQUMzQixRQUFNLEVBQUUsb0JBQW9CLElBQUk7QUFDaEMsUUFBTSxTQUFnQixDQUFDO0FBQ3ZCLE1BQUkscUJBQXFCO0FBQ3ZCLFVBQU0sZUFBZSxvQkFBb0IsTUFBTSxHQUFHO0FBQ2xELFFBQUksYUFBYSxTQUFTLE1BQU0sR0FBRztBQUVqQyxhQUFPO0FBQUEsUUFDTCxZQUFZO0FBQUEsVUFDVixLQUFLO0FBQUEsVUFDTCxrQkFBa0I7QUFBQSxRQUNwQixDQUFDO0FBQUEsTUFDSDtBQUFBLElBQ0Y7QUFDQSxRQUFJLGFBQWEsU0FBUyxRQUFRLEdBQUc7QUFDbkMsYUFBTztBQUFBLFFBQ0wsWUFBWTtBQUFBLFVBQ1YsS0FBSztBQUFBLFVBQ0wsV0FBVztBQUFBLFVBQ1gsa0JBQWtCO0FBQUEsUUFDcEIsQ0FBQztBQUFBLE1BQ0g7QUFBQSxJQUNGO0FBQUEsRUFDRjtBQUNBLFNBQU87QUFDVDs7O0FDM0IrUSxPQUFPLGlCQUFpQjtBQUV2UyxJQUFPLHVCQUFRLE1BQU07QUFDbkIsU0FBTyxZQUFZLENBQUMsQ0FBQztBQUN2Qjs7O0FQSUEsT0FBTyxVQUFVO0FBRWpCLElBQU8sa0JBQVEsQ0FBQyxTQUFjLFVBQVUsVUFBYztBQUNwRCxRQUFNLGNBQW1CLENBQUM7QUFDMUIsY0FBWSxLQUFLLElBQUksQ0FBQztBQUN0QixjQUFZLEtBQUssZUFBYSxDQUFDO0FBQy9CLGNBQVksS0FBSyxvQkFBaUIsSUFBSSxDQUFDO0FBQ3ZDLGNBQVksS0FBSyxtQkFBaUIsSUFBSSxDQUFDO0FBQ3ZDLGNBQVksS0FBSyxvQkFBa0IsT0FBTyxDQUFDO0FBQzNDLGNBQVksS0FBSyxjQUFZLENBQUM7QUFDOUIsY0FBWSxLQUFLLGlCQUFxQixNQUFNLE9BQU8sQ0FBQztBQUNwRCxjQUFZLEtBQUsscUJBQWtCLENBQUM7QUFDcEMsU0FBTztBQUNUOzs7QURqQkEsT0FBT0MsV0FBVTtBQUpqQixJQUFNQyxvQ0FBbUM7QUFLekMsSUFBTyxzQkFBUSxhQUFhLENBQUMsRUFBRSxNQUFNLFFBQVEsTUFBNkI7QUFDeEUsUUFBTSxNQUFNLFFBQVEsTUFBTSxRQUFRLElBQUksQ0FBQztBQUN2QyxTQUFPO0FBQUE7QUFBQTtBQUFBO0FBQUEsSUFJTCxNQUFNLElBQUk7QUFBQSxJQUNWLFNBQVM7QUFBQSxNQUNQLE9BQU87QUFBQSxRQUNMLEtBQUtDLE1BQUssUUFBUUMsbUNBQVcsSUFBSTtBQUFBLFFBQ2pDLEtBQUtELE1BQUssUUFBUUMsbUNBQVcsT0FBTztBQUFBLE1BQ3RDO0FBQUEsTUFDQSxZQUFZLENBQUMsUUFBUSxPQUFPLE9BQU8sUUFBUSxRQUFRLFNBQVMsUUFBUSxNQUFNO0FBQUEsSUFDNUU7QUFBQTtBQUFBLElBRUEsU0FBUyxnQkFBYyxLQUFLLFlBQVksT0FBTztBQUFBLElBQy9DLFFBQVE7QUFBQSxNQUNOLE1BQU07QUFBQSxNQUNOLE1BQU0sT0FBTyxJQUFJLGFBQWE7QUFBQSxNQUM5QixNQUFNO0FBQUEsTUFDTixPQUFPO0FBQUEsUUFDTCxDQUFDLElBQUksaUJBQWlCLEdBQUc7QUFBQSxVQUN2QixRQUFRO0FBQUEsVUFDUixjQUFjO0FBQUEsVUFDZCxJQUFJO0FBQUEsVUFDSixTQUFTLENBQUNELFVBQVNBLE1BQUssUUFBUSxJQUFJLE9BQU8sTUFBTSxJQUFJLGlCQUFpQixHQUFHLEVBQUU7QUFBQSxRQUM3RTtBQUFBLFFBQ0EsaUJBQWlCO0FBQUEsVUFDZixRQUFRLElBQUk7QUFBQSxVQUNaLGNBQWM7QUFBQSxVQUNkLElBQUk7QUFBQSxVQUNKLFNBQVMsQ0FBQ0EsVUFBU0EsTUFBSyxRQUFRLElBQUksT0FBTyxNQUFNLElBQUksaUJBQWlCLEdBQUcsRUFBRTtBQUFBLFFBQzdFO0FBQUEsUUFDQSxjQUFjO0FBQUEsVUFDWixRQUFRLElBQUk7QUFBQSxVQUNaLGNBQWM7QUFBQSxVQUNkLElBQUk7QUFBQSxVQUNKLFNBQVMsQ0FBQ0EsVUFBU0EsTUFBSyxRQUFRLElBQUksT0FBTyxNQUFNLElBQUksaUJBQWlCLEdBQUcsRUFBRTtBQUFBLFFBQzdFO0FBQUEsUUFDQSxhQUFhO0FBQUEsVUFDWCxRQUFRLElBQUk7QUFBQSxVQUNaLGNBQWM7QUFBQSxVQUNkLElBQUk7QUFBQSxVQUNKLFNBQVMsQ0FBQ0EsVUFBU0EsTUFBSyxRQUFRLElBQUksT0FBTyxNQUFNLElBQUksaUJBQWlCLEdBQUcsRUFBRTtBQUFBLFFBQzdFO0FBQUEsUUFDQyxTQUFTO0FBQUEsVUFDUixRQUFRLElBQUk7QUFBQSxVQUNaLGNBQWM7QUFBQSxVQUNkLElBQUk7QUFBQSxVQUNKLFNBQVMsQ0FBQ0EsVUFBU0EsTUFBSyxRQUFRLElBQUksT0FBTyxNQUFNLElBQUksaUJBQWlCLEdBQUcsRUFBRTtBQUFBLFFBQzdFO0FBQUEsTUFDRjtBQUFBLElBQ0Y7QUFBQSxJQUNBLEtBQUs7QUFBQSxNQUNILHFCQUFxQjtBQUFBLFFBQ25CLE1BQU07QUFBQSxVQUNKLG1CQUFtQjtBQUFBLFFBQ3JCO0FBQUEsTUFDRjtBQUFBLE1BQ0EsU0FBUztBQUFBLFFBQ1AsU0FBUztBQUFBLFVBQ1A7QUFBQSxZQUNFLGVBQWU7QUFBQSxZQUNmLFFBQVE7QUFBQSxjQUNOLFNBQVMsQ0FBQyxXQUFXO0FBQ25CLG9CQUFJLE9BQU8sU0FBUyxXQUFXO0FBQzdCLHlCQUFPLE9BQU87QUFBQSxnQkFDaEI7QUFBQSxjQUNGO0FBQUEsWUFDRjtBQUFBLFVBQ0Y7QUFBQSxRQUNGO0FBQUEsTUFDRjtBQUFBLElBQ0Y7QUFBQTtBQUFBLElBRUEsY0FBYztBQUFBLE1BQ1osU0FBUztBQUFBLFFBQ1A7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxRQUNBO0FBQUEsUUFDQTtBQUFBLFFBQ0E7QUFBQSxNQUNGO0FBQUEsSUFDRjtBQUFBLEVBQ0Y7QUFDRixDQUFDOyIsCiAgIm5hbWVzIjogWyJwYXRoIiwgIkVsZW1lbnRQbHVzUmVzb2x2ZXIiLCAiSWNvbnNSZXNvbHZlciIsICJfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSIsICJwYXRoIiwgIkVsZW1lbnRQbHVzUmVzb2x2ZXIiLCAiSWNvbnNSZXNvbHZlciIsICJfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSIsICJfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSIsICJwYXRoIiwgIl9fdml0ZV9pbmplY3RlZF9vcmlnaW5hbF9kaXJuYW1lIiwgInBhdGgiLCAiX192aXRlX2luamVjdGVkX29yaWdpbmFsX2Rpcm5hbWUiLCAicGF0aCIsICJfX3ZpdGVfaW5qZWN0ZWRfb3JpZ2luYWxfZGlybmFtZSJdCn0K