FIT5057 Project Management

FIT5057 Project Management

Semester 2, 2024

Assignment Two – Team Assignment

Due Dates:

● Group  submission  (5%): Team  Charter. There is a single Team Charter for each team, and one team member submits the file via Moodle as a team, and all the other team members must confirm the student statement in Moodle before the deadline: Week 6, 23.55 Monday 26 August 2024.

● Individual submission (75%): submit everything else as an individual via Moodle before the deadline: Week 10, 23.55 Monday, 30 September 2024.

● Group demonstration (20%): Each team will work collaboratively on Deliverable 3 and conduct a team demonstration in their designated applied classes:  During weeks 7 and 8 of the teaching period.

● Optional task- Engagement task. This task will attract up to 5 additional marks if you deliver a research- based presentation of up to 5 minutes in one of the unit workshops or applied classes on selected topics. Conditions apply.

Value: 50% of overall unit assessment

Mode of Submission: Online via Moodle

LEARNING OBJECTIVES

The aim of this assignment is to provide students with the opportunity (working as a team and as an individual) to learn and demonstrate their analytical and project management skills through the development of an information technology (IT) project management case study. The assignment aims to assess the following aspects of student learning about project management:

●   Skills in communicating projects.

●   Skills and knowledge from the Project Performance Domains

●   Working in a team.

SPECIFICATION AND DELIVERABLES

This assignment is based on the requirements of the NexaForge Studios. Student teams  undertaking the assignment are to play the role of project managers within an IT consultancy that NexaForge Studios has contracted to undertake a portfolio of projects that are intended to extend sales and bring in new customers (see below).

Note: students will not be required to develop a product as part of this process.

While the assignment requires the team to work together and support each other to plan the delivery of these projects, each team member will be responsible for one project. The project could be the one they proposed in their Assignment 1 or, could be a new topic if the students prefer to change their topic. The individual component of the assignment will be assessed based on the work submitted in Assignment 2 and not what was proposed in Assignment 1.

SUBMISSION INSTRUCTIONS

1.    The workshop topics will address different components of the assignment and you should actively

participate and engage in the classes, discuss and work on these as a team. Where required, present your progress to the tutor, who will provide support and advice.

2.   There are two written submissions:

a.  Group submission (Team Charter) due Week 6 (Monday 26 August, 11:55 PM).

b.  Individual submission (all deliverables) due Week 10 (Monday 30 September 2024, 11:55 PM).

3.    Submissions (group and individual) must take the form. of a single PDF document.

a.  Group submission should be named: <>_<>.pdf.

b.  Individual submission should be named: <>_<>_<>.pdf.

-   <> is Assignment 2.

-   <> is the group number that will be assigned to your group when you enrol yourself in groups through the Moodle portal.

-   <> is your unique Monash University student number.

-   For example, a file  name could be A2_0301_12345678.pdf, where the group number is ‘0301’ and the student ID is ‘12345678’ .

DEMONSTRATION INSTRUCTIONS

1.    Each team is required to demonstrate one Gantt chart (Deliverable 3, task 3.4) developed by one of the

team members, together with a reflective review of the team’s collaborative approach in developing work breakdown structures and Gantt charts, to their tutors during their allocated applied classes that occur in weeks 7 to 8.

2.    Your tutors will book a time for your team’s demonstration and communicate the time to you.

3.    All team members are expected to be present during demonstrations however, due to the time

constraints, only one team member should conduct the demonstration.

4.    You will receive verbal feedback on your demonstration and your assessment is entirely based on the

demonstration.

5.    You cannot request extensions for the demonstration tasks.

PLAGIARISM DECLARATION

When uploading your assignment on Moodle, you are required to accept the Student Statement (which includes a declaration that you have not plagiarised during the preparation of your assignment solution). You are required to ACCEPT the Student Statement; otherwise, you will not be able to submit your assignment electronically and your assignment will NOT be assessed.

NOTE : Marks may be deducted for any paragraphs or tables containing text which have been inserted into the document as screenshots or images. Any sections created in another word document or spreadsheet must be copied and pasted as text, (with the exception of the Gantt Chart, Risk Register and Stakeholder Registers).

GENERATIVE AI

AI & Generative AI tools may be used SELECTIVELY within this assessment. Where used, AI must be used responsibly, clearly documented and appropriately acknowledged (see Learn HQ).

Any work submitted for a mark must:

●     represent a sincere demonstration of your human efforts, skills and subject knowledge that you will be accountable for.

●    adhere to the guidelines for AI use set for the assessment task.

●     reflect the University’s commitment to academic integrity and ethical behaviour.

Inappropriate AI use and/or AI use without acknowledgement will be considered a breach of academic integrity.

LATE SUBMISSION

1.    Submission must be made by the due date. Unless an extension or special consideration has been

granted, or otherwise specified in the learning management system, students who submit an assessment task after the due date will receive a late-submission penalty of 5 percent of the available marks in that task. A further penalty of 5 percent of the available marks will be applied for each additional day (24-hour period), or part thereof, the assessment task is overdue.

2.    Assignments submitted more than seven days after the due date will not be marked.

3.    If you cannot complete an assessment (due to exceptional circumstances beyond your control), you

may be eligible for a short extension or special consideration. A short extension is two calendar days and is available once for each eligible assessment. The first short extension for a unit will be granted without a reason given. All subsequent extensions require a reason when the application is submitted. Special consideration is a longer extension (for more than two days). Eligible students must supply a reason and supporting documentation.

4.    For more details of the Special Consideration procedure and rules, and how to apply, visit:

https://www.monash.edu/students/admin/exams/changes/special-consideration

ASSIGNMENT TEAMS

WHAT IS A TEAM PROJECT?

A “Team Project” is NOT the same as a traditional “Group Project” .

In a “Team Project” the members of a team work on closely related problems (in this case projects within a portfolio of maximum 4 projects). Students submit some work as a team, in this case, (a) the “Team

Charter” . Students also submit other work as an individual, in this case, (b) the “Main Submission” (that is, all other components of the assignment).

Even though the “Main Submission” is an individual piece of work, you are expected to work alongside the other members of your team in the unit’s applied sessions as a number of elements of the individual “Main submission” depend upon decisions that you make collectively as a team (e.g. how to navigate your projects towards the success of your portfolio).

If, after reading the above, you are still unclear about the difference between a “Team Project” and a traditional “Group Project” then contact your tutor and/or Chief Examiner ([email protected]).

This assignment requires a collaborative effort by a team of students and includes both a team submission and an individual submission. Students must form. their teams (from attendees of the same tutorial/applied learning session) and elect a team leader to support the coordination of activities. Students should form. their teams in Week 4-5 applied class (no later than Week 6). Once confirmed, no change of team members is permitted without prior approval from the FIT5057 Chief Examiner (Dr. Roisin McNaney). Students should form. teams of 4 (if possible) or teams of 3 (if 4 is not possible, discuss with your tutor if this is the case). If you form a team of 3 people then you will still need to assume that you are splitting any budget and resources (if relevant) between 4 individual projects (with the 4th project being a ‘future endeavour’).

Context and Background

YOUR TEAM

You work in a project management consultancy partnership that undertakes contracts from clients and works with them to plan and manage internal service development projects that include IT design and development. The projects that you plan and manage are implemented by a combination of the client’s staff and external contractors that you source through the IT Contractor Rates Guide by HAYS (See the file uploaded to Moodle).

In phase 2 of the project, your client has reviewed the submitted business cases (project proposals) and shortlisted the 4 best proposals. You, as the project manager of one of the successful proposals, are now asked to join forces with up to 3 other successful project managers and form. a team to work towards achieving portfolio objectives in Assignments 2. Some of you have already formed a team during phase 1 (assignment

1). You will continue to collaborate with your teams.

* If you do not have a team at this stage please speak to your tutor.

THE CLIENT

NexaForge Studios is a market leader in the Double-A gaming industry. NexaForge specialises in crafting mid- market video games that blend professional development expertise with the creative spirit of indie studios. Their  titles   resonate  with   a  broad  audience,  striking  a  balance  between  innovation  and   accessibility. NexaForge’s clients comprise a diverse audience of gamers. They cater to console enthusiasts, PC players, and mobile gamers. Their  mid-market games  resonate  with those seeking engaging experiences  beyond AAA blockbusters.  NexaForge holds a significant market share in the Double-A gaming segment. Their focus on quality, innovation, and accessible gameplay sets them apart from their competitors including other Double- A studios, indie developers, and some larger non-indie studios. NexaForge competes by delivering polished experiences without the extensive budgets of AAA titles.

Their current annual revenue currently stands at an impressive $120 million. With their strategic push into new markets, they anticipate further growth. Senior management is expecting a 17% increase over the next

3 years. Through innovation and expansion to their game offerings. A diversified portfolio attracts different player demographics and opens new revenue streams Recognising the importance of player retention, they set another  KPI to  increase their  active  users  over the  same  period. They expect their  user  retention to increase: (i) Day 1 Retention (D1) – players who continue playing after the first day increase from the current 30% to 45%; (ii) Day 7 Retention (D7) – players who continue playing after the first week to increase from the current 12% to 30%; and (iii) Three-Month Retention (M3) – players whose interests are sustained over three months to change the current 8% to 16%.

THE CONTRACT

After the recent commercial success of their latest games, NexaForge executives have announced the creation of a portfolio package of up to 4 projects to extend sales and bring in new customers. They have dedicated a budget of $4.5M to spend on a portfolio of up to four projects to enter new markets and achieve their strategic goals of increasing revenue and increasing player retention.

Some technology aspects of the project, such as providing infrastructure and integrating the product into NexaForge’s platforms, are to be implemented by their in-house team of experts. However, all the remaining costs for the projects, and any profit that your partnership intends to make, must come out of the $4.5M budget.

Note that the $4.5M budget is for the entire portfolio of up to 4 projects and not just the one project you are proposing and therefore,  it  is  important to budget carefully where all expected costs and expenses  are justified and there are considerations for other potential projects in the portfolio. The portfolio of projects must be completed within 3 years of the contract start date. Your client did not specify the expected schedule for each  individual project proposal; however, they expect the schedule, budget and scope of work

(requirements, deliverables, delivery approach and cadence) to be balanced and feasible.

THE SUGGESTED PROJECTS

You can continue working on your proposed project from Assignment 1. However, if for any reason you prefer to change your topic, feel free to do so. You could choose one of the suggested  projects. The assessment of your submission will not be affected if you choose one of the suggested projects or change your topic from Assignment 1.

Note: If you and a colleague in your team have previously chosen the same topics for Assignment 1, one (or both) of you are required to change their topic for Assignment 2.

Project 1: Eidolon’s Veil

Eidolon’s Veil has a Surreal exploration and puzzle adventure genre that transports players to a dreamlike dimension where  reality  blurs  with  imagination. They  awaken  in  a  mysterious,  shifting world:  a  place  of forgotten memories, cryptic symbols, and hidden truths. The protagonist can transform. into different forms — a shadow, a wisp of light, or even an ancient tree. Each form. grants unique abilities. It includes symbolic puzzle solving by deciphering ancient glyphs, manipulating light and shadow, and altering the environment. The emotional explorations uncover fragments of memories tied to love, loss, and identity. Its visual style is hand-

painted landscapes that transition seamlessly between reality and abstraction. Useful references:

-     Gris: For its emotional impact, artistic visuals, and symbolic storytelling.

-     Journey: A poetic adventure emphasizing exploration and minimalistic narrative.

Project 2: Cipher Protocol

Cipher Protocol is in a Cyberpunk stealth-action genre and drops players into a dystopian metropolis ruled by mega-corporations. As a rogue hacker, they infiltrate secure facilities, manipulate surveillance systems, and expose dark secrets. The Dynamic Level Design enables Vertical exploration: climb skyscrapers, crawl through maintenance tunnels, and hack security drones. It has a morally complex story that includes choices that impact the city’s balance of power—corporate overlords, rebel factions, or anarchy.

Useful references:

-     Deus  Ex:  Human Revolution: A cyberpunk-themed game known for its intricate level design, stealth mechanics, and player choices.

-     Watch  Dogs:  Legion: Another cyberpunk-inspired title with hacking, open-world exploration, and a morally complex narrative.

Project 3: Retro Rivals

Retro Rivals has an arcade racing revival genre and has Nostalgic Vibes. Retro Rivals taps into the '80s arcade racing nostalgia. Players choose classic cars—sleek muscle cars, neon-lit speedsters, and quirky go-karts. Its aesthetics  are vibrant, retro visuals reminiscent of arcade cabinets and has an electrifying synthase soundtrack. Tracks pulse with synthwave energy and has time attack challenges e.g. beat ghost cars, set records, and unlock hidden shortcuts.

Useful references:

-     Horizon Chase Turbo: A modern take on classic arcade racing, complete with pixel art aesthetics and an '80s vibe.

-     OutRun: The iconic arcade racer that set the standard for high-speed, checkpoint-based gameplay.

Project 4: Stellar Nexus

Stellar Nexus is a Galactic Odyssey following a Sci-fi exploration adventure genre.  It invites players to captain their starship and explore a procedurally generated galaxy. Each star system holds secrets—ancient ruins, alien artefacts, and cosmic anomalies. Players can land on planets, engage in diplomacy with alien civilisations, and uncover lost technologies. Key features of this game are non-linear exploration where players follow their instincts,  artistic  visuals  (hand-painted  environments), and  emotional  storytelling  that  unravel  personal narratives hidden within dreams. The game doesn’t rely on a linear narrative. Instead, players encounter dynamic events, forge alliances, and influence the galactic balance. Players' decisions on diplomacy, trade, or conflict shape the galaxy’s fate.

Useful references:

-     No Man’s Sky: For its vast, procedurally generated universe and exploration mechanics.

-     Elite Dangerous: A space sim with realistic physics, trading, and exploration.

ASSIGNMENT TASKS

To formulate a successful portfolio, your team and you (as a project manager for your individual project) will complete the following deliverables following  a  predictive  (waterfall)  approach.  Discussion  of the Team Charter and the deliverables will be the focus of Workshops and Applied Sessions in weeks 5 to 9 of the semester:

●    Team Charter [Relevant content: workshop 4; applied 5]

●     Deliverable 1: Project Initiation [Relevant content: workshop 5]

●     Deliverable 2: Project Scope Management [Relevant content: workshop 5; applied 6]

●     Deliverable 3: Project Work Breakdown Structure and Project Schedule Management [Relevant content: workshop 5; workshop 6; applied 5; applied 6]

●     Deliverable 4: Project Cost Management [Relevant content: workshop 2; workshop 6; applied 7]

●     Deliverable 5: Project Risk Management [Relevant content: workshop 3; workshop 7; applied 8]

●     Deliverable 6: Project Quality Management [Relevant content: workshop 4; workshop 6; applied 7]

●     Deliverable  7:  Project Stakeholder and Communication  Management  [Relevant content:  workshop 2; workshop 7; applied 8]

Please  note that  here  is  a  writing  guide available  in  the  assignment  folder  in  Moodle  which contains a range of relevant templates that you can use to structure your report.

/* * @Author: liuhm * @Date: 2024-9-26 14:43:05 * @Last Modified by: liuhm * @Last Modified time: 2024-9-26 14:43:09 */ /** |-------------------------------------------------- | | 任务管理 | |-------------------------------------------------- */ <template> <el-card class="custom"> <!-- 头部 START --> <div slot="header" class="plr10 clearfix"> <div class="fl header-seach-box"> <el-form :model="searchFormData" class="fl mr5"> <el-cascader style="width: 200px" v-model="searchFormData.orgId" clearable class="ml10" filterable :options="maintenanceCompanyList" :props="{ value: 'id', label: 'name', emitPath: false, checkStrictly: true, children: 'childrenList' }" size="mini" placeholder="请选择管养单位" ></el-cascader> <el-select style="width: 200px" v-model="searchFormData.routeId" clearable class="ml10" filterable size="mini" placeholder="请选择巡查路线" > <el-option v-for="item in routerList" :key="item.id" :label="item.routeCode" :value="item.id" > </el-option> </el-select> <div v-if="hasFunctionCode(functionList, 'PERM_10244')" class="fr ml5"> <el-button size="mini" icon="el-icon-search " @click="searchTable" /> </div> </el-form> </div> </div> <!-- 头部 END --> <!-- 内容 START --> <el-table v-loading="tableLoading" size="mini" :data="dataSource" :height="`${cardContentHeight + 35}px`" row-key="id" style="width: 100%" border stripe :cell-class-name="cellClass" > <el-table-column label="序号" width="50" type="index" align="left" /> <el-table-column label="管养单位" prop="orgName" width="230" align="left" /> <el-table-column label="巡查时间" align="center" width="200" show-overflow-tooltip> <template slot-scope="scope"> <span>{{ `${formatDate(scope.row.inspectStartDate)} -${formatDate(scope.row.inspectEndDate)}` }}</span> </template> </el-table-column> <el-table-column label="巡查路线" prop="routeName" width="280" align="center" show-overflow-tooltip > <template slot-scope="scope"> <span>{{ scope.row.routeCode +scope.row.routeName }}</span> </template> </el-table-column> <el-table-column label="巡查状态" prop="status" width="100" align="center" show-overflow-tooltip > <template slot-scope="scope"> <span>{{ scope.row.status | DailyInspectionStatusMap }}</span> </template> </el-table-column> <el-table-column label="巡检人" prop="inspectUser" align="center" show-overflow-tooltip /> <el-table-column label="记录人" prop="recorder" align="center" show-overflow-tooltip > </el-table-column> <el-table-column label="操作" fixed="right" width="140" align="center"> <template slot-scope="scope"> <ul class="table-button-list"> <!-- <template v-if="hasFunctionCode(functionList, 'PERM_10249')"> <li @click="detailDailyInspection(scope.row)"> <img src="@/assets/images/table/chakan.png" />详情 </li> </template> --> <template v-if="hasFunctionCode(functionList, 'PERM_10246')"> <li @click="modifyDailyInspection(scope.row)"> <img src="@/assets/images/table/bianji.png" />编辑 </li> </template> <!-- <template v-if="hasFunctionCode(functionList, 'PERM_10247')"> <li @click="deleteDailyInspection(scope.row)"> <img src="@/assets/images/table/shanchu.png" />删除 </li> </template> --> </ul> <!-- <el-button-group> <template v-if="hasFunctionCode(functionList, 'PERM_10249')"> <el-button size="mini" type="primary" icon="el-icon-view" @click="detailDailyInspection(scope.row)" ></el-button> </template> <template v-if="hasFunctionCode(functionList, 'PERM_10246')"> <el-button size="mini" type="primary" icon="el-icon-edit-outline" @click="modifyDailyInspection(scope.row)" ></el-button> </template> <template v-if="hasFunctionCode(functionList, 'PERM_10247')"> <el-button size="mini" type="danger" icon="el-icon-delete" @click="deleteDailyInspection(scope.row)" ></el-button> </template> <template v-if="hasFunctionCode(functionList, 'PERM_10248')"> <el-button size="mini" icon="el-icon-document-checked" @click="completeDailyInspection(scope.row)" ></el-button> </template> </el-button-group> --> </template> </el-table-column> </el-table> <el-pagination class="mt10" @size-change="handleSizeChange" @current-change="handleCurrentChange" :current-page="page.pageNo" :page-sizes="[10, 20, 30, 40, 50]" :page-size="page.pageSize" layout="total, sizes, prev, pager, next, jumper" :total="entityCount" > </el-pagination> <!-- 内容 END --> <!-- 组件 START --> <template> <InspectionDialog ref="InspectionDialog" /> </template> <!-- 组件 END --> </el-card> </template> <script> // 接口 import { getDailyInspectionList, deleteDailyInspection, completeDailyInspection, getDailyDiseaseDetailPageList } from "@/api/dailyInspection"; import { getMaintenanceCompanyList, getRouteList } from "@/api/commonAnalysis" // 组件 import InspectionDialog from "./inspection-dialog/InspectionDialog"; // mixin import autoHeight from "@/mixin/autoHeight"; import { CONFIRM as CONF } from "@/optionsCfg/Message.js"; import { DIALOG_CLOSED_TYPE as DIALOG } from "@/optionsCfg/Constants"; import moment from "moment"; import { mapCfg } from "@/utils"; export default { name: "DailyInspection", components: { InspectionDialog, }, mixins: [autoHeight], data() { return { searchFormData: { orgId: "", // 管养单位id routeId: "", // 巡查路线id status: "", // 巡查状态 }, tableLoading: false, dataSource: [], page: { pageSize: 20, pageNo: 1, }, searchTime:[], entityCount: 0, routerList: [], maintenanceCompanyList:[], functionList: this.$store.getters.getFunction, deviceTypeOpt: [], // 设备类型配置 bridgeMonitorTypeOpt: [], // 监测类型列表 isFirstLoad: false, }; }, created() { this.isFirstLoad = true; this.getRouteList(); this.getMaintenanceCompanyList(); this.loadTableData(); }, filters: { //边坡与路线位置关系 DailyInspectionStatusMap(key) { const config = mapCfg("Inspection.Daliy.DailyInspectionStatus")(); let name = ""; config.forEach((item) => { if (item.key === key) { name = item.value; } }); return name; }, }, computed: { dailyInspectionStatusConfig() { const DailyInspectionStatus = mapCfg("Inspection.Daliy.DailyInspectionStatus")().map((item) => { return { value: item.key, label: item.value, }; }); return DailyInspectionStatus; }, }, methods: { // 加载表格数据 loadTableData() { this.tableLoading = true; const params = { pageSize: this.page.pageSize, pageNo: this.page.pageNo, status: this.searchFormData.status, routeId: this.searchFormData.routeId, orgId: this.searchFormData.orgId, startTime: this.searchTime && this.searchTime[0], endTime: this.searchTime && this.searchTime[1], }; getDailyInspectionList(params) .then((res) => { if (res) { this.dataSource = res.entities || []; this.entityCount = res.entityCount; if (res.pageCount < this.page.pageNo) { this.page.pageNo = res.pageCount; this.loadTableData(); } } }) .catch((e) => { this.$message.error(e && e.message); console.warn(e); }) .finally((_) => { this.tableLoading = false; }); }, // 搜索 searchTable() { this.page.pageNo = 1; this.loadTableData(); }, // 格式化时间 formatDate(date) { if (!date) return ""; return moment(date).format(`YYYY-MM-DD`); }, // 更改一页显示数量时 handleSizeChange(pageSize) { this.page.pageNo = 1; this.page.pageSize = pageSize; this.loadTableData(); }, // 分页改变时 handleCurrentChange(page) { this.page.pageNo = page; this.loadTableData(); }, // 获取养护单位列表 async getMaintenanceCompanyList() { try { const res = await getMaintenanceCompanyList(); if (res) { this.maintenanceCompanyList = res; } } catch (err) { console.log(err); } }, // 获取巡查路线列表编号 async getRouteList() { try { const res = await getRouteList(); if (res) { this.routerList = res; } } catch (err) { console.log(err); } }, // 添加边坡基本信息 addDailyInspection() { this.$refs["InspectionDialog"].open(); this.$refs["InspectionDialog"].onClosed = (args) => { if (DIALOG.CONFIRM === args.option) { this.page.pageNo = 1; this.loadTableData(); } }; }, //查看边坡基本信息 async detailDailyInspection(row) { try { // this.$store.commit("SHOW_WAITING"); this.$refs["InspectionDialog"].open(row); this.$refs["InspectionDialog"].onClosed = (args) => { if (DIALOG.CONFIRM === args.option) { // this.page.pageNo = 1 this.loadTableData(); } }; } catch (e) { console.warn(e); this.$store.commit("CLOSE_WAITING"); } }, // 修改边坡基本信息 async modifyDailyInspection(row) { try { this.$store.commit("SHOW_WAITING"); this.$refs["InspectionDialog"].open(row); this.$refs["InspectionDialog"].onClosed = (args) => { if (DIALOG.CONFIRM === args.option) { // this.page.pageNo = 1 this.loadTableData(); } }; } catch (e) { console.warn(e); this.$store.commit("CLOSE_WAITING"); }finally { this.$store.commit("CLOSE_WAITING"); } }, // 删除项目 async deleteDailyInspection(row) { try { await this.$confirm(CONF.DELETE, CONF.TITLE, { closeOnClickModal: false, confirmButtonText: CONF.YES, cancelButtonText: CONF.NO, type: "warning", }); } catch (e) { return; } // eslint-disable-line try { await this.deleteSideSlopeInfo({ dailyId : row.id }); } catch (e) { console.warn(e); this.$message.error(e && e.message); } }, // 删除项目接口 async deleteSideSlopeInfo(id) { try { this.$store.commit("SHOW_WAITING"); const res = await deleteDailyInspection(id); if (res) { this.page.pageNo = 1; this.loadTableData(); this.$message.success("删除成功"); } } catch (e) { console.warn(e); this.$message.error(e && e.message); } finally { this.$store.commit("CLOSE_WAITING"); } }, cellClass({ row, column, rowIndex, columnIndex }) { if (columnIndex === 3) return "table-align"; }, async uploadBridgeDeviceInfo(file) { // 设备导入 this.$store.commit("SHOW_WAITING"); try { const formData = new FormData(); formData.append("file", file.file); const res = await importBridgeDeviceInfo(formData); if (res) { this.$message.success("导入成功"); this.page.pageNo = 1; this.loadTableData(); } } catch (e) { console.warn(e); this.$message.error(e && e.message); } finally { this.$store.commit("CLOSE_WAITING"); } }, downLoadFile() { if (!this.searchFormData.bridgeId) { this.$message.warning("请选择桥梁"); return; } const bridgeList = this.$store.state.bridge.bridgeList.filter( (item) => item.id === this.searchFormData.bridgeId ); if (bridgeList[0] && bridgeList[0].hasOwnProperty("bridgeNo")) { const bridgeCode = bridgeList[0].bridgeNo; downloadMonitorDeviceInfo(bridgeCode) .then((res) => { if (res && res.message) { console.warn(res); this.$message.error(res && res.message); } }) .catch((e) => { this.$message.error(e && e.message); console.warn(e); }); } else { this.$message.warning("暂无桥梁"); } }, }, }; </script> <style lang="scss" scoped> ::v-deep .el-button-group > .el-button:not(:last-child) { margin-right: 0; } .header-seach-box { height: 46px; box-sizing: border-box; padding: 9px 0; } ::v-deep .table-align .el-tooltip { padding-left: 28%; text-align: left; } ::v-deep .el-table th > .cell { color: #000; } ::v-deep .el-table th { background-color: #eff3f8; } ::v-deep .el-table--striped .el-table__body tr.el-table__row--striped td { background-color: rgb(239, 243, 248, 0.6); } .table-button-list{ grid-template-columns: repeat(1, 1fr); } </style>// 巡查任务弹窗 <template> <el-dialog :title="dialogTitle" :visible.sync="dialogVisible" width="600px" :close-on-click-modal="false" @closed="handleClosed" > <el-form ref="inspectionForm" :model="formData" :rules="formRules" label-width="100px" size="mini" :disabled="viewMode" > <el-row> <el-col :span="12"> <el-form-item label="管养单位" prop="orgId"> <el-cascader v-model="formData.orgId" :options="maintenanceCompanyList" :props="{ value: 'id', label: 'name', checkStrictly: true, children: 'childrenList', emitPath: false }" placeholder="请选择管养单位" style="width: 100%" clearable ></el-cascader> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="任务名称" prop="taskName"> <el-input v-model="formData.taskName" placeholder="请输入任务名称"></el-input> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="巡检路线" prop="routeId"> <el-select v-model="formData.routeId" placeholder="请选择巡检路线" style="width: 100%"> <el-option v-for="item in routeList" :key="item.id" :label="item.name" :value="item.id" ></el-option> </el-select> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="状态" prop="status"> <el-radio-group v-model="formData.status" placeholder="请选择状态" style="width: 100%"> <el-radio label="启用" :value="true"></el-radio> <el-radio label="禁用" :value="false"></el-radio> </el-radio-group> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="12"> <el-form-item label="巡检周期" prop="inspectionPeriod"> <el-date-picker v-model="formData.inspectionPeriod" type="daterange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期" value-format="yyyy-MM-dd" style="width: 100%" ></el-date-picker> </el-form-item> </el-col> <el-col :span="12"> <el-form-item label="巡检频次" prop="frequency"> <el-select v-model="formData.frequency" placeholder="请选择巡检频次" style="width: 100%"> <el-option v-for="item in frequencyOptions" :key="item.value" :label="item.label" :value="item.value" ></el-option> </el-select> </el-form-item> </el-col> </el-row> <el-form-item label="备注"> <el-input v-model="formData.remark" type="textarea" :rows="3" placeholder="请输入备注信息" ></el-input> </el-form-item> </el-form> <div slot="footer" class="dialog-footer"> <el-button size="mini" @click="dialogVisible = false">取 消</el-button> <el-button v-if="!viewMode" type="primary" size="mini" @click="submitForm">确 定</el-button> </div> </el-dialog> </template> <script> import { getMaintenanceCompanyList } from '@/api/commonAnalysis'; import { getRouteList } from '@/api/basicInformation'; // import { addInspectionTask, updateInspectionTask, getInspectionTaskDetail } from '@/api/conservation'; // 假设这些API已存在 export default { name: 'InspectionDialog', data() { return { dialogVisible: false, dialogType: 'add', // add, edit, view formData: { id: '', orgId: null, taskName: '', routeId: '', frequency: '', inspectionPeriod: [], remark: '' }, formRules: { orgId: [{ required: true, message: '请选择管养单位', trigger: 'change' }], taskName: [{ required: true, message: '请输入任务名称', trigger: 'blur' }], routeId: [{ required: true, message: '请选择巡检路线', trigger: 'change' }], frequency: [{ required: true, message: '请选择巡检频次', trigger: 'change' }], inspectionPeriod: [{ required: true, message: '请选择巡检周期', trigger: 'change' }] }, maintenanceCompanyList: [], routeList: [], frequencyOptions: [ { label: '每日', value: 'daily' }, { label: '每周', value: 'weekly' }, { label: '每月', value: 'monthly' }, { label: '每季度', value: 'quarterly' }, { label: '每半年', value: 'semiannually' }, { label: '每年', value: 'annually' } ], onClosed: null }; }, computed: { dialogTitle() { const titleMap = { add: '新建巡查任务', edit: '修改巡查任务', view: '巡查任务详情' }; return titleMap[this.dialogType] || '巡查任务'; }, viewMode() { return this.dialogType === 'view'; } }, created() { this.getMaintenanceCompanyList(); this.getRouteList(); }, methods: { // 打开弹窗 open(type = 'add', data = null) { this.dialogType = type; this.dialogVisible = true; this.resetForm(); if (data && (type === 'edit' || type === 'view')) { this.loadTaskDetail(data.id); } return new Promise((resolve) => { this.onClosed = resolve; }); }, // 重置表单 resetForm() { this.formData = { id: '', orgId: null, taskName: '', routeId: '', frequency: '', inspectionPeriod: [], remark: '' }; // 如果表单已经被渲染,则重置校验结果 this.$nextTick(() => { this.$refs.inspectionForm && this.$refs.inspectionForm.clearValidate(); }); }, // 加载任务详情 async loadTaskDetail(id) { try { this.$store.commit('SHOW_WAITING'); const res = await getInspectionTaskDetail({ id }); if (res) { // 假设API返回的数据结构与表单一致 this.formData = { ...res, inspectionPeriod: [res.startDate, res.endDate] }; } } catch (e) { console.warn(e); this.$message.error(e && e.message); } finally { this.$store.commit('CLOSE_WAITING'); } }, // 提交表单 submitForm() { this.$refs.inspectionForm.validate(async (valid) => { if (!valid) return; try { this.$store.commit('SHOW_WAITING'); // 处理日期范围 const params = { ...this.formData, startDate: this.formData.inspectionPeriod[0], endDate: this.formData.inspectionPeriod[1] }; delete params.inspectionPeriod; let res; if (this.dialogType === 'add') { res = await addInspectionTask(params); } else { res = await updateInspectionTask(params); } if (res) { this.$message.success(`${this.dialogType === 'add' ? '新增' : '修改'}成功`); this.dialogVisible = false; this.onClosed && this.onClosed({ option: 'confirm', data: res }); } } catch (e) { console.warn(e); this.$message.error(e && e.message); } finally { this.$store.commit('CLOSE_WAITING'); } }); }, // 弹窗关闭回调 handleClosed() { this.onClosed && this.onClosed({ option: 'cancel' }); }, // 获取管养单位列表 async getMaintenanceCompanyList() { try { const res = await getMaintenanceCompanyList(); if (res) { this.maintenanceCompanyList = res; } } catch (e) { console.warn(e); this.$message.error(e && e.message); } }, // 获取路线列表 async getRouteList() { try { const res = await getRouteList(); if (res) { this.routeList = res; } } catch (e) { console.warn(e); this.$message.error(e && e.message); } } } }; </script> <style lang="scss" scoped> .dialog-footer { text-align: right; } </style> <template> <div class="project-management-block"> <!-- 弹窗组件 --> <side-slope-dialog :visible="dialogVisible" :mode="currentMode" :initial-form="currentForm" @update:visible="dialogVisible = $event" @success="handleDialogSuccess" /> <div class="project-management-block--header"> <el-form size="mini" :inline="true" class="fr ml10" align="left"> <el-input v-model="searchParams.projectName" placeholder="项目名称或编号" size="mini" clearable @clear="resetSearch" style="width: 140px;" class="mr10" /> <el-select v-model="searchParams.status" placeholder="项目状态" size="mini" clearable @clear="resetSearch" style="width: 140px;" class="mr10"> <el-option v-for="item in statusOptions" :key="item.value" :label="item.label" :value="item.value" filterable /> </el-select> <el-date-picker v-model="searchProjectDate" value-format="timestamp" size="mini" class="mr10" type="datetimerange" range-separator="至" start-placeholder="开始日期" end-placeholder="结束日期"> </el-date-picker> <el-button type="primary" size="mini" style="width: 80px;" @click="handleSearch" class="mr10" >查询 </el-button> <el-button type="success" size="mini" style="width: 80px;" @click="handleAction('create')" class="mr10" >新建 </el-button> </el-form> </div> <div class="project-management-block--content" > <!-- 项目数据表格 --> <el-table :data="tableData" border stripe style="width: 100%; flex: 1;" height="100%" row-key="id" > <el-table-column label="序号" type="index" width="120"> </el-table-column> <el-table-column label="项目名称" prop="projectName" width="250"> </el-table-column> <el-table-column label="项目编号" prop="projectCode" width="220"> </el-table-column> <el-table-column label="项目周期" width="250"> <template slot-scope="scope"> <span v-if="scope.row">{{ `${formatDate(scope.row.projectStartDate)} 至 ${formatDate(scope.row.projectEndDate)}` }}</span> </template> </el-table-column> <el-table-column label="项目状态" width="150"> <template slot-scope="scope"> {{ getStatusText(scope.row.status)}} </template> </el-table-column> <el-table-column label="边坡总数" prop="sideSlopeTotalCount" width="150"> </el-table-column> <el-table-column label="已完成边坡数" prop="sideSlopeCompleteCount" width="194"> </el-table-column> <el-table-column label="完成率" width="150"> <template slot-scope="scope"> {{ `${((scope.row.sideSlopeCompleteCount / scope.row.sideSlopeTotalCount) * 100).toFixed(1)}%` }} </template> </el-table-column> <el-table-column label="操作" width="195"> <template slot-scope="scope"> <div v-if="scope.row.id" style="display: flex; justify-content: center;"> <el-button size="mini" type="primary" @click="handleAction('view', scope.row)" style="padding: 4px 8px; font-size: 12px" >查看 </el-button> <el-button size="mini" type="info" @click="handleAction('edit', scope.row)" style="padding: 4px 8px; font-size: 12px" >编辑 </el-button> <el-button size="mini" type="danger" @click="handleAction('delete', scope.row)" style="padding: 4px 8px; font-size: 12px" >删除 </el-button> </div> </template> </el-table-column> </el-table> </div> <div class="project-management-block--footer"> <el-pagination background layout="total,prev, pager, next,jumper" :current-page.sync="pageParams.pageNo" :page-size="pageParams.pageSize" :total="pageParams.total"/> </div> </div> </template> <script> // 导入模块 import SideSlopeDialog from './SideSlopeDialog.vue' // 项目详情弹窗组件 import { mapCfg } from '@/utils' // 字典配置映射工具 //接口 import { getPeriodicInspectionPageList, deletePeriodicInspection } from '../../api/testProject' import moment from 'moment' // 组件定义 export default { name: 'ProjectManagement', // 组件名称 components: { SideSlopeDialog // 注册弹窗组件 }, // 数据模型 data() { return { // 搜索参数对象 searchParams: { projectName: '', // 项目名称/编号搜索词 status: '', // 项目状态筛选值 projectStartDate: '', // 项目开始日期 projectEndDate: '' // 项目结束日期 }, // 日期范围选择器绑定值 searchProjectDate: [], // 表格数据源 tableData: [], // 分页参数 pageParams: { pageNo: 1, // 当前页码 pageSize: 10, // 每页条数 total: 0 // 总数据量 }, // 弹窗控制参数 dialogVisible: false, // 弹窗显示状态 currentMode: '', // 弹窗模式: 'create'/'edit'/'view' currentForm: null // 当前表单数据 } }, // 监听器 watch: { //监听日期范围选择器变化:将选择器值同步到搜索参数对象 searchProjectDate(newVal) { if (newVal && newVal.length === 2) { this.searchParams.projectStartDate = newVal[0] this.searchParams.projectEndDate = newVal[1] } else { this.searchParams.projectStartDate = '' this.searchParams.projectEndDate = '' } } }, // 生命周期钩子 async created() { // 加载表格数据 this.loadTableData() }, computed:{ statusOptions() { const dictList = mapCfg('Inspection.Periodic.PeriodicInspectionStatus')() return dictList.map(item => ({ value: item.key, label: item.value })) }, }, // 方法/函数 methods: { // 工具方法 //根据状态值获取状态文本 getStatusText(statusValue) { const option = this.statusOptions.find((opt) => opt.value === statusValue) return option.label }, // 格式化时间 formatDate(date) { if (!date) return ""; return moment(date).format(`YYYY-MM-DD`); }, // 弹窗操作方法 handleAction(action, row = null) { if (action === 'create') { // 新建操作 this.currentMode = 'create' this.currentForm = { projectCode: '', projectName: '', projectStartDate: '', projectEndDate: '', projectUser: '', remark: '', sideSlopeDetailList: [] } this.dialogVisible = true } else if (action === 'edit') { // 编辑操作 this.currentMode = 'edit' this.currentForm = { ...row, projectStartDate: row.projectStartDate, projectEndDate: row.projectEndDate } this.dialogVisible = true } else if (action === 'view') { // 查看操作 this.currentMode = 'view' this.currentForm = { ...row, projectStartDate: row.projectStartDate, projectEndDate: row.projectEndDate } this.dialogVisible = true } else if (action === 'delete') { // 删除操作 this.deleteItem(row) } }, //删除方法 async deleteItem(row) { try { // 显示确认对话框 await this.$confirm('确定要删除该项目吗?', '提示', { confirmButtonText: '确定', cancelButtonText: '取消', type: 'warning' }) // 调用删除API await deletePeriodicInspection({ periodicId: row.id }) this.$message.success('删除成功') // 如果删除的是最后一页的最后一条,返回上一页 if (this.tableData.length === 1 && this.pageParams.pageNo > 1) { this.pageParams.pageNo -= 1 } // 重新加载数据 this.loadTableData() } catch (error) { // 用户取消删除时不显示错误 if (error !== 'cancel') { this.$message.error('删除失败') }} }, // 弹窗操作成功回调,刷新表格数据(返回第一页) handleDialogSuccess() { this.pageParams.pageNo = 1 this.loadTableData() }, // 数据操作方法 //加载表格数据,根据搜索参数和分页设置获取项目数据 async loadTableData() { // 转换日期为时间戳格式 const startTime = this.searchParams.projectStartDate ? new Date(this.searchParams.projectStartDate).getTime() : null const endTime = this.searchParams.projectEndDate ? new Date(this.searchParams.projectEndDate).getTime() : null // 构造API参数 const params = { pageNo: this.pageParams.pageNo, pageSize: this.pageParams.pageSize, searchKey: this.searchParams.projectName, status: this.searchParams.status, startTime, endTime } try { // 调用API获取项目数据 const res = await getPeriodicInspectionPageList(params) this.tableData = res.entities || [] this.pageParams.total = res.entityCount || 0 } catch (error) { console.error('加载项目列表失败', error) this.tableData = [] this.pageParams.total = 0 } }, //搜索方法重置到第一页并重新加载数据 handleSearch() { this.pageParams.pageNo = 1 this.loadTableData() }, //重置搜索条件,清空所有搜索参数并重新加载数据 resetSearch() { this.searchParams = { projectName: '', status: '', projectStartDate: '', projectEndDate: '' } this.searchProjectDate = [] this.handleSearch() }, } } </script> <style lang="scss" scoped> .project-management-block { width: 100%; height: 100%; position: relative; background-color: #fff; &--header { height: 50px; box-sizing: border-box; padding: 10px; border-bottom: 1px solid #ebeef5; } &--content { display: block; padding: 10px; height: calc(100% - 90px); position: relative; z-index: 10; } &--footer { border-top: 1px solid #ebeef5; padding: 4px 10px 0 10px; position: relative; z-index: 11; text-align: center; } } /* 优化表格样式 */ ::v-deep .el-table { flex: 1; min-height: 0; // 防止flex布局溢出 /* 单元格样式 */ th, td { padding: 4px 0; } /* 表头样式 */ .el-table__header-wrapper { line-height: 1; th { .cell { line-height: 1.2; } } } } /* 分页组件样式优化,确保在不同缩放比例下可见 */ ::v-deep .el-pagination { padding: 4px 5px; .btn-prev, .btn-next, .el-pager li, .el-pagination__jump { min-width: 28px; height: 28px; line-height: 28px; margin: 0 2px; } .el-pagination__total, .el-pagination__jump { font-size: 12px; } } /* 响应式调整 */ @media screen and (max-width: 1200px) { .project-management-block--header .el-col { margin-bottom: 8px; } ::v-deep .el-pagination { display: flex; flex-wrap: wrap; justify-content: center; &>* { margin-bottom: 5px; } } } </style>/*-----------弹窗模块管理------------------- */ <template> <el-dialog :title="dialogMode === 'create' ? '新建' : dialogMode === 'edit' ? '修改' : '查看'" :visible.sync="dialogVisible" :modal-append-to-body="true" append-to-body :close-on-click-modal="false" custom-class="fixed-height-dialog" width="60%" top="5vh"> <el-form label-width="80px" ref="formRef" :model="currentForm" style="height: 100%; display: flex; flex-direction: column;" :rules="rules"> <!-- 项目信息区域 --> <div class="formBorder"> <el-row :gutter="10"> <el-col :span="6"> <el-form-item size="mini" label="项目名称" prop="projectName"> <el-input v-model="currentForm.projectName" clearable style="width:100%" size="mini" :disabled="dialogMode === 'view'"> </el-input> </el-form-item> </el-col> <el-col :span="6"> <el-form-item size="mini" label="项目编号" prop="projectCode"> <el-input v-model="currentForm.projectCode" clearable style="width:100%" size="mini" :disabled="dialogMode === 'view'"> </el-input> </el-form-item> </el-col> <el-col :span="12"> <el-form-item size="mini" label="项目周期" prop="projectDate"> <el-date-picker v-model="projectDate" range-separator="→" start-placeholder="请选择开始日期" end-placeholder="请选择结束日期" type="daterange" size="mini" style="width: 100%;" unlink-panels :disabled="dialogMode === 'view'"> </el-date-picker> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="6"> <el-form-item label="负责人" size="mini" style="width: fit-content;"> <el-input v-model="currentForm.projectUser" clearable style="width:100%" size="mini" :disabled="dialogMode === 'view'"> </el-input> </el-form-item> </el-col> </el-row> <el-row> <el-col :span="24"> <el-form-item label="项目概述"> <el-input v-model="currentForm.remark" :rows="2" :disabled="dialogMode === 'view'"> </el-input> </el-form-item> </el-col> </el-row> </div> <div class="formBorder2"> <el-container style="height: 100%;"> <el-header style="height: auto; flex-shrink: 0; padding-bottom: 10px;"> <el-row :gutter="10" type="flex" class="searchDialog"> <el-col :span="5"> <el-select v-model="filterForm.maintenanceCompanyName" placeholder="请选择管养单位" size="mini" clearable filterable @clear="resetSearch" :disabled="dialogMode === 'view'"> <el-option v-for="item in MaintenanceUnitoptions" :key="item.value" :label="item.label" :value="item.value"> </el-option> </el-select> </el-col> <el-col :span="5"> <el-select v-model="filterForm.routeCode" placeholder="请选择路线编号" size="mini" clearable filterable @clear="resetSearch" :disabled="dialogMode === 'view'"> <el-option v-for="item in routeCodeOptions" :key="item.value" :label="item.label" :value="item.value"> </el-option> </el-select> </el-col> <el-col :span="5"> <el-input v-model="filterForm.searchKey" placeholder="请输入边坡编号或名称" size="mini" clearable @keyup.enter.native="searchForm" @clear="resetSearch" :disabled="dialogMode === 'view'"> <i slot="suffix" class="el-input__icon el-icon-search"></i> </el-input> </el-col> <el-col :span="5"> <el-select v-model="filterForm.evaluateLevel" placeholder="请选择技术状态等级" size="mini" clearable @clear="resetSearch" :disabled="dialogMode === 'view'"> <el-option v-for="item in getEvaluateLevel" :key="item.value" :label="item.label" :value="item.value" /> </el-select> </el-col> <el-col :span="2" :offset="4"> <el-button type="primary" size="mini" style="width:100%" icon="el-icon-search" @click="searchForm" :loading="loading" :disabled="dialogMode === 'view'">搜索 </el-button> </el-col> </el-row> </el-header> <el-main style="overflow-y: hidden;"> <el-table ref="scrollTable" v-loading="loading" style="width: 100%;" border :data="formTabledata" :row-style="{ height: '36px' }" :cell-style="{ padding: '4px 0', textAlign: 'center' }" :header-cell-style="{ height: '36px', padding: '4px 0', lineHeight: '36px', textAlign: 'center' }" @selection-change="handleSelectionChange" :row-key="getRowkey"> <el-table-column type="selection" width="55" :selectable="isRowSelectable" :reserve-selection="true"> </el-table-column> <el-table-column label="管养单位" prop="maintenanceCompanyName" width="290" show-overflow-tooltip> </el-table-column> <el-table-column label="路线编号" prop="routeCode" width="100"> </el-table-column> <el-table-column label="边坡编号" prop="sideSlopeCode" width="240" show-overflow-tooltip> </el-table-column> <el-table-column label="边坡名称" prop="sideSlopeName" width="267" show-overflow-tooltip> </el-table-column> <el-table-column label="技术状态等级" width="137"> <template slot-scope="scope"> {{ mapEvaluateLevel(scope.row.evaluateLevel) }} </template> </el-table-column> </el-table> </el-main> <!-- 分页区域 --> <el-footer style="flex-shrink: 0; padding-top: 10px;"> <el-pagination background @current-change="handleCurrentChange" :current-page="pageParams.pageNo" :page-size="pageParams.pageSize" layout="total, prev, pager, next" :total="total"> </el-pagination> </el-footer> </el-container> </div> </el-form> <!-- 弹窗底部按钮 --> <div slot="footer" class="dialog-footer" v-if="dialogMode === 'create' || dialogMode === 'edit'"> <el-button @click="dialogVisible = false">取消 </el-button> <el-button type="primary" @click="submitForm">提交 </el-button> </div> </el-dialog> </template> <script> import { mapCfg } from "@/utils"; import { getPeriodicInspectionSideSlopePageList, addPeriodicInspection, modifyPeriodicInspection, getSelectedPeriodicInspectionSideSlopeList } from "../../api/testProject"; import { getMaintenanceCompanyList, getRouteList } from "../../api/basicInformation"; export default { name: "SideSlopeDialog", props: { visible: Boolean, // 控制弹窗显示 mode: String, // 模式:create/edit/view initialForm: Object, // 初始表单数据 }, data() { return { isInitializingSelection: false, // 初始化状态标志 dialogVisible: this.visible, // 弹窗显示状态 dialogMode: this.mode, // 当前模式 currentForm: { projectCode: '', // 项目编号 projectName: '', // 项目名称 projectStartDate: '', // 开始日期 projectEndDate: '', // 结束日期 projectUser: '', // 项目负责人 remark: '', // 备注 sideSlopeDetailList: [] // 边坡详情列表 }, projectDate: [], // 项目日期范围 total: 0, // 总数据量 loading: false, // 加载状态 pageParams: { // 分页参数 pageNo: 1, pageSize: 10, }, filterForm: { // 搜索条件 maintenanceCompanyName: "", routeCode: "", searchKey: "", evaluateLevel: "", }, mulitipleSelection: [], allSelection: new Map(), MaintenanceUnitoptions: [], // 管养单位选项 routeCodeOptions: [], // 路线编号选项 formTabledata: [], // 表格数据 rules: { // 表单验证规则 projectName: [ { required: true, message: "项目名称不能为空", trigger: "blur" }, ], projectCode: [ { required: true, message: "项目编码不能为空", trigger: "blur" }, ], }, }; }, watch: { //监听visible变化时确保加载数据 async visible(val) { this.dialogVisible = val; if (val) { // 打开对话框时重置状态 this.resetAllData(); if (this.dialogMode !== 'create' && this.currentForm.id) { // 确保在DOM更新后加载数据 await this.$nextTick(); await this.LoadListData(); } } else { // 关闭对话框时重置数据 this.resetAllData(); } }, //监听模式变化 mode(val) { this.dialogMode = val; // 当模式从创建变为编辑/查看时,确保加载数据 if (this.dialogVisible && val !== 'create' && this.currentForm.id) { this.LoadListData(); } }, // 修复3:监听初始表单数据变化 initialForm: { deep: true, immediate: true, async handler(val) { this.currentForm = { ...val }; // 安全处理日期(新增空值判断) this.projectDate = val.projectStartDate && val.projectEndDate // 关键修复:添加空值校验 if (this.dialogVisible && val.id && this.dialogMode !== 'create') { await this.$nextTick(); await this.LoadListData(); } } }, // 同步弹窗显示状态到父组件 dialogVisible(val) { this.$emit("update:visible", val); }, projectDate: { deep: true, handler(value) { if (value && value.length === 2) { this.currentForm.projectStartDate = value[0]; this.currentForm.projectEndDate = value[1]; } }, }, }, async created() { // 初始化数据 this.getRouteList(); this.getMaintenanceCompanyList(); }, computed:{ getEvaluateLevel() { const levelList = mapCfg("Inspection.Regular.RegularEvaluateLevel")(); return levelList.map((item) => ({ value: item.key, label: item.value, })) }, }, methods: { handleSelectionChange(selection) { // 跳过初始化阶段的选中状态变更 if (this.isInitializingSelection) return; // 获取当前页选中项的key集合 const currentPageKeys = new Set( selection.map(row => row.sideSlopeUniqueCode) ); // 处理当前页的取消选中操作 this.formTabledata.forEach(row => { const key = row.sideSlopeUniqueCode; // 仅当行在全局选中池中但不在当前页选中集合时删除 if (this.allSelection.has(key) && !currentPageKeys.has(key)) { this.allSelection.delete(key); } }); // 添加新选中的项到全局池 selection.forEach(row => { const key = row.sideSlopeUniqueCode; if (!this.allSelection.has(key)) { this.allSelection.set(key, row); } }); // 更新当前页选中引用 this.mulitipleSelection = selection; }, getRowkey(row) { return row.sideSlopeUniqueCode; }, // 判断行是否可选(查看模式禁用选择) isRowSelectable(row, index) { return this.dialogMode !== "view"; }, // 获取管养单位列表 async getMaintenanceCompanyList() { const res = await getMaintenanceCompanyList(); this.MaintenanceUnitoptions = res.map((item) => ({ value: item, label: item, })); }, // 获取路线列表 async getRouteList() { const res = await getRouteList(); this.routeCodeOptions = res.map((item) => ({ value: item.id, label: item.routeCode, })); }, // 搜索方法 searchForm() { this.pageParams.pageNo = 1; this.LoadListData(); }, // 重置搜索条件 resetSearch() { this.filterForm = { maintenanceCompanyName: "", routeCode: "", searchKey: "", evaluateLevel: "", }; this.pageParams.pageNo = 1; this.LoadListData(); }, // 重置组件状态 resetAllData() { this.resetSelection(); this.formTabledata = []; // 清空表格数据 this.total = 0; // 重置总条数 this.pageParams = { // 重置分页 pageNo: 1, pageSize: 10 }; // 重置搜索条件(可选) this.filterForm = { maintenanceCompanyName: "", routeCode: "", searchKey: "", evaluateLevel: "" }; }, // 修改原有方法 resetSelection() { this.allSelection.clear(); this.allSelection = new Map(); if (this.$refs.scrollTable) { this.$refs.scrollTable.clearSelection(); } }, // 映射技术状态等级 mapEvaluateLevel(level) { const option = this.getEvaluateLevel.find( (item) => item.value === level ); return option.label; }, // 加载表格数据 async LoadListData() { this.loading = true; try { const params = { orgId: this.filterForm.maintenanceCompanyName, routeId: this.filterForm.routeCode, searchKey: this.filterForm.searchKey, evaluateLevel: this.filterForm.evaluateLevel, pageSize: this.pageParams.pageSize, pageNo: this.pageParams.pageNo, }; // 获取表格数据 const res = await getPeriodicInspectionSideSlopePageList(params); this.formTabledata = res.entities; this.total = res.entityCount; // 处理非创建模式的数据加载 if (this.dialogMode !== 'create' && this.currentForm.id) { // 首次加载时获取所有选中项 if (this.pageParams.pageNo === 1) { const selected = await getSelectedPeriodicInspectionSideSlopeList({ periodicId: this.currentForm.id, pageSize: 10000, // 获取所有选中项 pageNo: 1 }); // 重置全局选中池 this.allSelection.clear(); this.mulitipleSelection = selected.entities; // 存储全局选中状态 this.mulitipleSelection.forEach(item => { this.allSelection.set(item.sideSlopeUniqueCode, item); }); } // 设置当前页选中状态 this.isInitializingSelection = true; this.$nextTick(() => { this.formTabledata.forEach(row => { if (this.allSelection.has(row.sideSlopeUniqueCode)) { this.$refs.scrollTable.toggleRowSelection(row, true); } }); this.isInitializingSelection = false; }); } } catch (error) { console.error("加载数据失败:", error); this.$message.error("加载数据失败"); } finally { this.loading = false; } }, // 当前页码变化 handleCurrentChange(val) { this.pageParams.pageNo = val; this.LoadListData(); }, // 提交表单 async submitForm() { this.$refs.formRef.validate(async (valid) => { if (valid) { // 验证是否选择了边坡 const selectedItems = Array.from(this.allSelection.values()); if (this.allSelection.size === 0) { this.$message.warning("请至少选择一个边坡"); return; } // 构造提交参数 const params = { ...this.currentForm, sideSlopeDetailList: selectedItems.map(item => ({ sideSlopeUniqueCode: item.sideSlopeUniqueCode, evaluateLevel: item.evaluateLevel, evaluateDate: item.evaluateDate || undefined })), }; // 根据模式选择操作 const action = this.dialogMode === "create" ? addPeriodicInspection : modifyPeriodicInspection; // 执行操作 try { const success = await action(params); if (success) { this.$message.success( this.dialogMode === "create" ? "新建成功" : "修改成功" ); this.$refs.scrollTable.clearSelection(); this.$emit("success"); this.dialogVisible = false; } else { this.$message.error("操作失败"); } } catch (error) { this.$message.error(error.message || "操作失败"); } } }); } }, }; </script> <style lang="scss" scoped> /* 修复1:弹性容器最小高度约束 */ :deep(.fixed-height-dialog), .formBorder2, .formBorder2 .el-container, .formBorder2 .el-main { min-height: 0 !important; } /* 表格行高优化 */ :deep(.el-table) { .el-table__row { height: 36px !important; td { padding: 4px 0 !important; } } .el-table__header { th { padding: 4px 0 !important; .cell { line-height: 28px !important; } } } /* 确保内部滚动 */ .el-table__body-wrapper { overflow-y: auto !important; max-height: calc(100vh - 400px) !important; } } /* 表单区域固定 */ .formBorder { position: relative; ///为伪元素提供定位上下文 border: thin dotted black !important; padding: 10px !important; margin-top: 15px !important; flex-shrink: 0 !important; height: auto !important; overflow: visible !important; margin-bottom: 15px !important; /* 边框的文字 */ &::before { content: "项目信息"; position: absolute; top: -8px; //调整到更合适的位置 left: 15px; //向右移动避免遮挡 background-color: #fff; //背景色需与页面背景一致 padding: 0 8px; font-size: 13px; color: #606266; z-index: 10; //提高层级确保显示 font-weight: 500; //加粗文字 pointer-events: none; //防止点击穿透 } } .formBorder2 { position: relative; border: thin dotted black; padding: 10px; flex: 1; min-height: 0; overflow: hidden; // 保留 display: flex; flex-direction: column; padding-top: 25px; // 新增 &::before { content: "待检边坡"; position: absolute; top: 5px; // 调整到新增的padding区域内部 left: 15px; background-color: #fff; padding: 0 8px; font-size: 13px; color: #606266; z-index: 1000; font-weight: 500; pointer-events: none; } .el-container { height: auto !important; // 覆盖行内样式 flex: 1; // 填满剩余空间 display: flex; flex-direction: column; min-height: 0; .el-header { flex-shrink: 0; height: auto !important; padding-bottom: 10px; } .el-main { flex: 1; overflow: hidden; position: relative; padding: 0; } .el-footer { flex-shrink: 0; padding-top: 10px; } } } // 弹窗底部按钮区域 .dialog-footer { padding: 10px 20px; border-top: 1px solid #ebeef5; text-align: center; } // 搜索区域样式 .searchDialog { margin-top: 5px; } // 空数据样式 :deep(.el-table__empty-block) { min-height: 200px; display: flex; justify-content: center; align-items: center; } // 分页样式 :deep(.el-pagination) { padding: 5px 0; } // 表格高度控制 :deep(.el-table) { height: 100% !important; .el-table__body-wrapper { overflow-y: auto !important; } } </style>结合前两段代码的组件方式 后两段代码应该如何修改
09-04
<think>嗯,用户给了一个Vue项目的代码片段,包含四个组件:DailyInspection、InspectionDialog、ProjectManagement、SideSlopeDialog。现在用户的问题是,结合前两段代码的组件方式,后两段代码应该如何修改。 首先,我需要理解前两段代码的组件结构和交互方式。前两段是DailyInspection和InspectionDialog,它们之间的关系是父组件DailyInspection通过ref调用子组件InspectionDialog的open方法来打开对话框,并传递数据。当对话框关闭时,通过onClosed回调来刷新父组件的数据。 接下来,用户提到的后两段代码是ProjectManagement和SideSlopeDialog。这两个组件目前是分开的,可能需要调整它们的交互方式,使其类似前两段的结构。例如,ProjectManagement作为父组件,应该通过ref调用SideSlopeDialog的open方法,并在操作后刷新数据。 首先检查ProjectManagement组件。它有一个SideSlopeDialog组件,通过ref引用,并有一个handleAction方法,当点击编辑或查看时传递数据。但当前的SideSlopeDialog组件使用的是props(visible, mode, initialForm)来控制状态和数据。需要调整SideSlopeDialog的open方法,使其能够接收参数,并处理不同模式下的数据加载。 然后,调整SideSlopeDialog组件的open方法,使其接受参数,比如类型(add、edit、view),以及数据。同时,SideSlopeDialog内部需要处理不同模式下的初始化,比如加载已有的数据。当对话框关闭时,应该触发一个回调,通知父组件刷新数据。 可能还需要调整SideSlopeDialog的数据初始化逻辑,确保在打开对话框时正确加载数据,尤其是在编辑或查看模式下。例如,在visible的watch中,当对话框打开时,如果处于非创建模式,需要调用LoadListData方法加载相关数据。 另外,需要确保父组件ProjectManagement在调用SideSlopeDialog的open方法时,能够正确传递初始数据,并在对话框关闭后处理提交操作,刷新表格数据。可能需要修改ProjectManagement的handleAction方法,使其在打开对话框时传递正确的参数,并设置onClosed回调来触发数据刷新。 还需要检查SideSlopeDialog中的currentForm数据结构,确保其与ProjectManagement中的currentForm一致,可能需要调整属性名称,比如将projectStartDate和projectEndDate映射到projectDate,以便在日期选择器中正确显示。 最后,确保SideSlopeDialog的submitForm方法在提交成功后,能够触发父组件的刷新,可能需要通过$emit事件或者在onClosed回调中处理。 总结修改步骤: 1. 在SideSlopeDialog中添加open方法,处理不同模式下的数据加载。 2. 调整ProjectManagement中的handleAction方法,使用ref调用SideSlopeDialog的open方法,并设置回调。 3. 修改SideSlopeDialog的visible和initialForm的watch,确保正确初始化数据。 4. 确保表单数据的正确映射,如日期范围。 5. 处理对话框关闭后的回调,刷新父组件数据。</think>结合前两段代码的组件交互方式,后两段代码的修改建议如下: 1. **SideSlopeDialog组件修改**: ```html <!-- 在SideSlopeDialog中添加open方法 --> methods: { open(type = 'create', data = null) { this.dialogMode = type; this.dialogVisible = true; this.isInitializingSelection = true; if (type === 'create') { this.resetAllData(); this.currentForm = { projectCode: '', projectName: '', projectStartDate: '', projectEndDate: '', projectUser: '', remark: '', sideSlopeDetailList: [] }; this.projectDate = []; } else { this.currentForm = { ...data }; this.projectDate = [data.projectStartDate, data.projectEndDate]; this.$nextTick(() => { this.LoadListData(); }); } return new Promise((resolve) => { this.onClosed = resolve; }); } } ``` 2. **ProjectManagement组件修改**: ```html <!--ProjectManagement中调整handleAction方法 --> methods: { handleAction(action, row = null) { if (action === 'create') { this.$refs.sideSlopeDialog.open('create') .then(() => { this.loadTableData(); }); } else if (action === 'edit') { this.$refs.sideSlopeDialog.open('edit', row) .then(() => { this.loadTableData(); }); } else if (action === 'view') { this.$refs.sideSlopeDialog.open('view', row); } else if (action === 'delete') { this.deleteItem(row); } } } ``` 3. **SideSlopeDialog组件watch调整**: ```html watch: { visible(val) { this.dialogVisible = val; if (val && this.dialogMode !== 'create') { this.LoadListData(); } }, initialForm: { deep: true, handler(val) { if (val && this.dialogVisible) { this.currentForm = { ...val }; this.projectDate = [val.projectStartDate, val.projectEndDate]; } } } } ``` 4. **SideSlopeDialog组件提交逻辑优化**: ```html <!-- 在submitForm方法中添加回调处理 --> submitForm() { this.$refs.formRef.validate(async (valid) => { if (valid) { // ...现有验证逻辑 try { const success = await action(params); if (success) { this.$message.success(this.dialogMode === "create" ? "新建成功" : "修改成功"); this.$refs.scrollTable.clearSelection(); this.dialogVisible = false; this.onClosed && this.onClosed({ option: 'confirm' }); } } catch (error) { // ...错误处理 } } }); } ``` 5. **ProjectManagement模板调整**: ```html <!-- 在组件引用处添加ref --> <side-slope-dialog ref="sideSlopeDialog" @success="handleDialogSuccess" /> ```
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值