hejianhao
2025-05-09 a0c49d7a675d55c73a76eb466202619929f44ec2
项目组管理
7个文件已修改
2个文件已添加
695 ■■■■■ 已修改文件
laboratory/src/layouts/components/HeaderNav.vue 5 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/router/index.js 21 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/utils/request.js 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/login/index.vue 11 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/projectList/addProject.vue 28 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/projectList/detailProject.vue 236 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/projectList/editProject.vue 288 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/projectList/index.vue 77 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/views/projectList/service.js 27 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
laboratory/src/layouts/components/HeaderNav.vue
@@ -73,7 +73,10 @@
    },
    // 跳转标签
    goTag(tag) {
      this.$router.push(tag.path)
        this.$router.push({
            path: tag.path,
            query: tag.query
        })
    },
    handleWheel(e) {
      if (this.scrollTimer) {
laboratory/src/router/index.js
@@ -120,6 +120,24 @@
                    keepAlive: true,
                },
                component: () => import("../views/projectList/addProject"),
            },
            {
                path: "editProject",
                name: "EditProject",
                meta: {
                    title: "编辑项目组",
                    hide: true,
                },
                component: () => import("../views/projectList/editProject"),
            },
            {
                path: "detailProject",
                name: "DetailProject",
                meta: {
                    title: "项目组详情",
                    hide: true,
                },
                component: () => import("../views/projectList/detailProject"),
            }
        ]
    },
@@ -701,7 +719,8 @@
            const tagInfo = {
                path: to.path,
                name: to.name,
                meta: to.meta
                meta: to.meta,
                query: to.query,
            }
            tagList.push(tagInfo)
            sessionStorage.setItem('tagList', JSON.stringify(tagList))
laboratory/src/utils/request.js
@@ -57,10 +57,10 @@
    }
    if (res.data.code == 200) {
      console.log('res',res)
      if (!res.data) {
        return Promise.resolve({})
      }
      console.log('res', res.data.data || res.data)
      return Promise.resolve(res.data.data || res.data)
    } else {
      if (res.data.code == 103 || res.data.code == 401) {
laboratory/src/views/login/index.vue
@@ -68,11 +68,20 @@
    // 添加窗口大小变化监听器
    window.addEventListener('resize', this.handleResize)
  },
  mounted() {
    document.addEventListener("keydown", this.handleKeyDown);
  },
  destroyed() {
    // 组件销毁时移除监听器
    window.removeEventListener('resize', this.handleResize)
    document.removeEventListener("keydown", this.handleKeyDown);
  },
  methods: {
    handleKeyDown(event) {
      if (event.key === 'Enter') {
        this.login()
      }
    },
    // 添加处理窗口大小变化的方法
    handleResize() {
      this.viewWidth = window.innerWidth
@@ -87,7 +96,7 @@
        return
      }
      loginReq(this.loginForm).then(res => {
        console.log('111111',res)
        console.log('111111', res)
        sessionStorage.setItem('token', res.token)
        sessionStorage.setItem('userInfo', JSON.stringify(res.userInfo.user))
        this.$router.push('/system')
laboratory/src/views/projectList/addProject.vue
@@ -31,7 +31,7 @@
                </div>
            </div>
            <div class="add-project-footer">
                <el-button type="primary">保存</el-button>
                <el-button @click="submitForm" type="primary">保存</el-button>
            </div>
        </template>
        <SelectMember ref="selectMember" @submit="selectUser" />
@@ -39,6 +39,7 @@
</template>
<script>
import { addProject } from './service'
export default {
    name: 'AddProject',
    data() {
@@ -60,7 +61,30 @@
        submitForm() {
            this.$refs.form.validate((valid) => {
                if (valid) {
                    console.log('submit!')
                    if (this.selectMemberData.length == 0) {
                        this.$message.error('请选择项目组成员')
                        return
                    }
                    const ROLE_NAME_TO_TYPE = {
                        '审批人': 2,
                        '工艺工程师': 3,
                        '实验员': 4,
                        '化验师': 5
                    };
                    const data = {
                        teamName: this.form.teamName,
                        personCharge: this.form.personCharge,
                        staffs: this.selectMemberData.map(member => ({
                            userId: member.userId,
                            roleType: ROLE_NAME_TO_TYPE[member.roleName]
                        }))
                    }
                    addProject(data).then(res => {
                        if (res.code == 200) {
                            this.$message.success('添加成功')
                            this.$router.push({ name: 'ProjectList' })
                        }
                    })
                }
            })
        },
laboratory/src/views/projectList/detailProject.vue
New file
@@ -0,0 +1,236 @@
<template>
    <Card>
        <template>
            <el-form disabled ref="form" :model="form" :rules="rules" inline label-position="top">
                <el-form-item prop="teamName" label="项目组名称">
                    <el-input v-model="form.teamName" placeholder="请输入" />
                </el-form-item>
                <el-form-item prop="personCharge" label="项目组负责人">
                    <el-input v-model="form.personCharge" placeholder="请输入" />
                </el-form-item>
            </el-form>
            <div class="header-title">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>项目组成员</div>
                </div>
            </div>
            <div class="member-list">
                <div v-for="item in 4" :key="item" class="member-list-card">
                    <div class="member-item">
                        <div class="member-title">{{ ['审批人', '工艺工程师', '实验员', '化验师'][item - 1] }}</div>
                        <div :class="item == 1 || item == 2 ? 'member-name-box' : 'member-name-box-2'">
                            <el-tooltip v-for="i in memberList(item)" :key="i.userId" class="member-name" effect="dark"
                                :content="i.nickName" placement="top">
                                <span>{{ i.nickName }}</span>
                            </el-tooltip>
                        </div>
                    </div>
                </div>
            </div>
        </template>
        <SelectMember ref="selectMember" @submit="selectUser" />
    </Card>
</template>
<script>
import { getProjectDetail } from './service'
export default {
    name: 'EddProject',
    data() {
        return {
            form: {},
            rules: {
                teamName: [{ required: true, message: '请输入项目组名称', trigger: 'blur' }],
                personCharge: [{ required: true, message: '请输入项目组描述', trigger: 'blur' }]
            },
            selectMemberData: [],
            ROLE_NAME_TO_TYPE: {
                '审批人': 2,
                '工艺工程师': 3,
                '实验员': 4,
                '化验师': 5
            }
        }
    },
    created() {
        getProjectDetail({ id: this.$route.query.id }).then(res => {
            this.form = {
                teamName: res.teamName,
                personCharge: res.personCharge
            }
            this.selectMemberData = res.staffs.map(item => ({
                userId: item.userId,
                roleName: ['审批人', '工艺工程师', '实验员', '化验师'][item.roleType - 2],
                nickName: item.nickName
            }))
        })
    },
    methods: {
        addMember() {
            this.$refs.selectMember.open()
        },
        memberList(i) {
            switch (i) {
                case 1:
                    return this.selectMemberData.filter(item => item.roleName == '审批人')
                case 2:
                    return this.selectMemberData.filter(item => item.roleName == '工艺工程师')
                case 3:
                    return this.selectMemberData.filter(item => item.roleName == '实验员')
                case 4:
                    return this.selectMemberData.filter(item => item.roleName == '化验师')
                default:
                    break;
            }
        },
    }
}
</script>
<style scoped lang="less">
.el-form--inline .el-form-item {
    margin-right: 83px;
}
.header-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        img {
            width: 12px;
            height: 19px;
        }
        div {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: 'Source Han Sans CN Bold Bold';
            &:before {
                content: '*';
                color: #F56C6C;
                margin-right: 4px;
            }
        }
    }
}
.member-list {
    margin-top: 18px;
    display: flex;
    flex-wrap: wrap;
    gap: 28px;
    .member-list-card {
        position: relative;
        width: 340px;
        height: 400px;
        border-radius: 8px;
        border: 1px solid #DCDFE6;
        &:nth-child(1) {
            background: linear-gradient(to bottom, rgba(4, 156, 154, 0.2) 0%, rgba(5, 242, 194, 0) 70%);
        }
        &:nth-child(2) {
            background: linear-gradient(to bottom, rgba(5, 160, 193, 0.2) 0%, rgba(5, 242, 194, 0) 70%);
        }
        &:nth-child(3) {
            background: linear-gradient(to bottom, rgba(255, 77, 79, 0.20) 0%, rgba(255, 242, 194, 0) 70%);
        }
        &:nth-child(4) {
            background: linear-gradient(to bottom, rgba(250, 199, 20, 0.21) 0%, rgba(255, 242, 194, 0) 70%);
        }
        .member-item {
            height: 100%;
            display: flex;
            flex-direction: column;
            .member-title {
                margin-top: 20px;
                width: 100%;
                font-family: 'Source Han Sans CN Bold Bold';
                font-weight: bold;
                font-size: 16px;
                color: rgba(0, 0, 0, 0.8);
                line-height: 16px;
                text-align: center;
            }
            .member-name-box {
                flex: 1;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            .member-name-box-2 {
                padding: 0 20px;
                padding-top: 40px;
                display: grid;
                grid-template-columns: repeat(4, 1fr);
                align-items: flex-start;
                flex-wrap: wrap;
                gap: 20px;
                justify-content: center;
            }
            .member-name {
                width: 60px;
                height: 60px;
                background: #7D8B79;
                border-radius: 50%;
                text-align: center;
                line-height: 60px;
                font-weight: 500;
                font-size: 16px;
                color: #FFFFFF;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
                box-sizing: border-box;
            }
            .member-edit {
                cursor: pointer;
                position: absolute;
                bottom: 10px;
                left: 50%;
                transform: translateX(-50%);
                font-weight: 400;
                font-size: 12px;
                color: #FF4D4F;
                line-height: 22px;
                width: 40px;
                background: #FFF1F0;
                border-radius: 4px;
                border: 1px solid #FFCCC7;
                text-align: center;
            }
        }
    }
}
.add-project-footer {
    margin-top: 43px;
    button {
        width: 220px;
    }
}
</style>
laboratory/src/views/projectList/editProject.vue
New file
@@ -0,0 +1,288 @@
<template>
    <Card>
        <template>
            <el-form ref="form" :model="form" :rules="rules" inline label-position="top">
                <el-form-item prop="teamName" label="项目组名称">
                    <el-input v-model="form.teamName" placeholder="请输入" />
                </el-form-item>
                <el-form-item prop="personCharge" label="项目组负责人">
                    <el-input v-model="form.personCharge" placeholder="请输入" />
                </el-form-item>
            </el-form>
            <div class="header-title">
                <div class="header-title-left">
                    <img src="@/assets/public/headercard.png" />
                    <div>项目组成员</div>
                </div>
                <el-button class="el-icon-plus" type="primary" @click="addMember"> 添加项目组成员</el-button>
            </div>
            <div class="member-list">
                <div v-for="item in 4" :key="item" class="member-list-card">
                    <div class="member-item">
                        <div class="member-title">{{ ['审批人', '工艺工程师', '实验员', '化验师'][item - 1] }}</div>
                        <div :class="item == 1 || item == 2 ? 'member-name-box' : 'member-name-box-2'">
                            <el-tooltip v-for="i in memberList(item)" :key="i.userId" class="member-name" effect="dark"
                                :content="i.nickName" placement="top">
                                <span>{{ i.nickName }}</span>
                            </el-tooltip>
                        </div>
                        <div class="member-edit" v-if="memberList(item).length != 0" @click="editUserList">修改</div>
                    </div>
                </div>
            </div>
            <div class="add-project-footer">
                <el-button @click="submitForm" type="primary">保存</el-button>
            </div>
        </template>
        <SelectMember ref="selectMember" @submit="selectUser" />
    </Card>
</template>
<script>
import { getProjectDetail, editProject } from './service'
export default {
    name: 'EddProject',
    data() {
        return {
            form: {},
            rules: {
                teamName: [{ required: true, message: '请输入项目组名称', trigger: 'blur' }],
                personCharge: [{ required: true, message: '请输入项目组描述', trigger: 'blur' }]
            },
            selectMemberData: [],
            // 角色配置常量
            ROLE_CONFIG: {
                1: { key: 'approver', limit: 1, label: '审批人' },
                2: { key: 'engineer', limit: 1, label: '工艺工程师' },
            },
            ROLE_NAME_TO_TYPE: {
                '审批人': 2,
                '工艺工程师': 3,
                '实验员': 4,
                '化验师': 5
            }
        }
    },
    created() {
        getProjectDetail({ id: this.$route.query.id }).then(res => {
            this.form = {
                teamName: res.teamName,
                personCharge: res.personCharge
            }
            this.selectMemberData = res.staffs.map(item => ({
                userId: item.userId,
                roleName: ['审批人', '工艺工程师', '实验员', '化验师'][item.roleType - 2],
                nickName: item.nickName
            }))
        })
    },
    methods: {
        submitForm() {
            this.$refs.form.validate((valid) => {
                if (valid) {
                    if (this.selectMemberData.length == 0) {
                        this.$message.error('请选择项目组成员')
                        return
                    }
                    const data = {
                        id: this.$route.query.id,
                        teamName: this.form.teamName,
                        personCharge: this.form.personCharge,
                        staffs: this.selectMemberData.map(member => ({
                            userId: member.userId,
                            roleType: this.ROLE_NAME_TO_TYPE[member.roleName]
                        }))
                    }
                    editProject(data).then(res => {
                        if (res.code == 200) {
                            this.$message.success('添加成功')
                            this.$router.push({ name: 'ProjectList' })
                        }
                    })
                }
            })
        },
        addMember() {
            this.$refs.selectMember.open()
        },
        memberList(i) {
            switch (i) {
                case 1:
                    return this.selectMemberData.filter(item => item.roleName == '审批人')
                case 2:
                    return this.selectMemberData.filter(item => item.roleName == '工艺工程师')
                case 3:
                    return this.selectMemberData.filter(item => item.roleName == '实验员')
                case 4:
                    return this.selectMemberData.filter(item => item.roleName == '化验师')
                default:
                    break;
            }
        },
        selectUser(data) {
            for (const [roleId, config] of Object.entries(this.ROLE_CONFIG)) {
                const members = data.filter(item => item.roleName === config.label);
                if (members.length > config.limit) {
                    this.$message.error(`${config.label}最多只能选择${config.limit}个`);
                    return
                }
            }
            this.selectMemberData = data;
            this.$refs.selectMember.close();
        },
        editUserList() {
            this.$refs.selectMember.open();
            this.$nextTick(() => {
                this.$refs.selectMember.setSelection(this.selectMemberData);
            });
        }
    }
}
</script>
<style scoped lang="less">
.el-form--inline .el-form-item {
    margin-right: 83px;
}
.header-title {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 13px;
    .header-title-left {
        display: flex;
        align-items: center;
        gap: 13px;
        img {
            width: 12px;
            height: 19px;
        }
        div {
            flex-shrink: 0;
            font-weight: bold;
            font-size: 18px;
            color: #222222;
            line-height: 27px;
            font-family: 'Source Han Sans CN Bold Bold';
            &:before {
                content: '*';
                color: #F56C6C;
                margin-right: 4px;
            }
        }
    }
}
.member-list {
    margin-top: 18px;
    display: flex;
    flex-wrap: wrap;
    gap: 28px;
    .member-list-card {
        position: relative;
        width: 340px;
        height: 400px;
        border-radius: 8px;
        border: 1px solid #DCDFE6;
        &:nth-child(1) {
            background: linear-gradient(to bottom, rgba(4, 156, 154, 0.2) 0%, rgba(5, 242, 194, 0) 70%);
        }
        &:nth-child(2) {
            background: linear-gradient(to bottom, rgba(5, 160, 193, 0.2) 0%, rgba(5, 242, 194, 0) 70%);
        }
        &:nth-child(3) {
            background: linear-gradient(to bottom, rgba(255, 77, 79, 0.20) 0%, rgba(255, 242, 194, 0) 70%);
        }
        &:nth-child(4) {
            background: linear-gradient(to bottom, rgba(250, 199, 20, 0.21) 0%, rgba(255, 242, 194, 0) 70%);
        }
        .member-item {
            height: 100%;
            display: flex;
            flex-direction: column;
            .member-title {
                margin-top: 20px;
                width: 100%;
                font-family: 'Source Han Sans CN Bold Bold';
                font-weight: bold;
                font-size: 16px;
                color: rgba(0, 0, 0, 0.8);
                line-height: 16px;
                text-align: center;
            }
            .member-name-box {
                flex: 1;
                display: flex;
                align-items: center;
                justify-content: center;
            }
            .member-name-box-2 {
                padding: 0 20px;
                padding-top: 40px;
                display: grid;
                grid-template-columns: repeat(4, 1fr);
                align-items: flex-start;
                flex-wrap: wrap;
                gap: 20px;
                justify-content: center;
            }
            .member-name {
                width: 60px;
                height: 60px;
                background: #7D8B79;
                border-radius: 50%;
                text-align: center;
                line-height: 60px;
                font-weight: 500;
                font-size: 16px;
                color: #FFFFFF;
                overflow: hidden;
                text-overflow: ellipsis;
                white-space: nowrap;
                box-sizing: border-box;
            }
            .member-edit {
                cursor: pointer;
                position: absolute;
                bottom: 10px;
                left: 50%;
                transform: translateX(-50%);
                font-weight: 400;
                font-size: 12px;
                color: #FF4D4F;
                line-height: 22px;
                width: 40px;
                background: #FFF1F0;
                border-radius: 4px;
                border: 1px solid #FFCCC7;
                text-align: center;
            }
        }
    }
}
.add-project-footer {
    margin-top: 43px;
    button {
        width: 220px;
    }
}
</style>
laboratory/src/views/projectList/index.vue
@@ -22,26 +22,26 @@
                </el-form>
            </template>
            <template #setting>
                <el-button @click="handleAddProject" class="el-icon-plus" type="primary">
                <el-button @click="handleProject('add')" class="el-icon-plus" type="primary">
                    新增项目组</el-button>
            </template>
            <template #table>
                <el-table-column prop="teamName" label="项目组名称" />
                <el-table-column prop="personCharge" label="项目负责人" />
                <el-table-column prop="staffName" label="项目组成员" />
                <el-table-column prop="age" label="项目创建时间" />
                <el-table-column prop="age" label="状态">
                <el-table-column prop="createTime" label="项目创建时间" />
                <el-table-column prop="status" label="状态">
                    <template #default="{ row }">
                        <el-tag v-if="row.status == 1" type="success">正常运作</el-tag>
                        <el-tag v-else type="danger">已封存</el-tag>
                    </template>
                </el-table-column>
                <el-table-column prop="age" label="操作">
                <el-table-column label="操作">
                    <template #default="{ row }">
                        <el-button type="text" @click="handleChangeStatus(row, 1)">封存</el-button>
                        <el-button type="text" @click="handleChangeStatus(row, 0)">解封</el-button>
                        <el-button type="text">编辑</el-button>
                        <el-button type="text">详情</el-button>
                        <el-button v-if="row.status == 1" type="text" @click="handleChangeStatus(row, 2)">封存</el-button>
                        <el-button v-if="row.status == 2" type="text" @click="handleChangeStatus(row, 1)">解封</el-button>
                        <el-button type="text" @click="handleProject('edit', row.id)">编辑</el-button>
                        <el-button type="text" @click="handleProject('detail', row.id)">详情</el-button>
                        <el-button type="text" @click="handleDel(row)">删除</el-button>
                    </template>
                </el-table-column>
@@ -54,7 +54,7 @@
</template>
<script>
import { getProjectList } from './service'
import { getProjectList, changeStatus, deleteProject } from './service'
import moment from 'moment'
export default {
    name: 'ProjectList',
@@ -62,6 +62,7 @@
        return {
            showDelConfirm: false,
            rowId: '',
            status: null,
            changeStatus: false,
            changeStatusTitle: '',
            changeStatusTip: '',
@@ -77,34 +78,60 @@
        this.getList()
    },
    methods: {
        handleAddProject() {
            this.$router.push({
                path: '/projectList/addProject'
            })
        handleProject(type, id) {
            if (type == 'add') {
                this.$router.push({
                    path: '/projectList/addProject'
                })
                return
            }
            if (type == 'edit') {
                this.$router.push({
                    path: '/projectList/editProject',
                    query: {
                        id
                    }
                })
                return
            }
            if (type == 'detail') {
                this.$router.push({
                    path: '/projectList/detailProject',
                    query: {
                        id
                    }
                })
                return
            }
        },
        handleDel(row) {
            this.rowId = row.id
            this.showDelConfirm = true
        },
        handleDelConfirm() {
            this.showDelConfirm = false
            this.msgsuccess('删除成功')
            this.rowId = ''
            this.getList()
            deleteProject({ id: this.rowId }).then(res => {
                this.showDelConfirm = false
                this.msgsuccess('删除成功')
                this.rowId = ''
                this.getList()
            })
        },
        handleChangeStatus(row, status) {
            this.rowId = row.id
            this.changeStatusTitle = status == 1 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 1 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
            this.status = status
            this.changeStatusTitle = status == 2 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 2 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
            this.changeStatus = true
        },
        handleChangeStatusConfirm() {
            this.changeStatus = false
            this.msgsuccess('操作成功')
            this.rowId = ''
            this.changeStatusTitle = ''
            this.changeStatusTip = ''
            this.getList()
            changeStatus({ id: this.rowId, status: this.status }).then(res => {
                this.changeStatus = false
                this.msgsuccess('操作成功')
                this.rowId = ''
                this.changeStatusTitle = ''
                this.changeStatusTip = ''
                this.getList()
            })
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
laboratory/src/views/projectList/service.js
@@ -3,4 +3,29 @@
// 列表
export const getProjectList = (data) => {
    return axios.post('/api/t-project-team/pageList', { ...data })
}
}
// 新增
export const addProject = (data) => {
    return axios.post('/api/t-project-team/add', { ...data })
}
// 编辑
export const editProject = (data) => {
    return axios.post('/api/t-project-team/update', { ...data })
}
// 详情
export const getProjectDetail = (data) => {
    return axios.get(`/open/t-project-team/getDetailById?id=${data.id}`)
}
// 修改项目组状态
export const changeStatus = (data) => {
    return axios.post('/api/t-project-team/upAndDown', { ...data })
}
// 删除项目组
export const deleteProject = (data) => {
    return axios.delete(`/open/t-project-team/deleteById?id=${data.id}`)
}