13404089107
2025-04-01 5a9eed686fe1bf10a095e6b56fbef4188832fe00
Merge branch 'main' of http://120.76.84.145:10101/gitblit/r/H5/leshan-laboratory
11个文件已修改
1 文件已重命名
12个文件已添加
1163 ■■■■■ 已修改文件
.gitignore 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/App.vue 46 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/public/close-l@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/public/layoutsBG.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/public/logOut.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/public/notice@2x.png 补丁 | 查看 | 原始文档 | blame | 历史
src/assets/public/photo.png 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Card/index.vue 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/Table/index.vue 125 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/TableSlot/index.vue 74 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/components/showDelConfirm/index.vue 2 ●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/components/AppContent.vue 18 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/components/ElMenu/MenuItem.vue 31 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/components/ElMenu/index.vue 4 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/components/HeaderNav.vue 183 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/layouts/index.vue 91 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/main.js 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/router/index.js 128 ●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/store/index.js 23 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/confirmation-sheet/index.vue 100 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dataManagement/approvalPlan/list.vue 152 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/dataManagement/dispatching/list.vue 补丁 | 查看 | 原始文档 | blame | 历史
src/views/projectList/addProject.vue 19 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
src/views/projectList/index.vue 123 ●●●●● 补丁 | 查看 | 原始文档 | blame | 历史
.gitignore
@@ -1,7 +1,7 @@
.DS_Store
node_modules
/dist
package-lock.json
# local env files
.env.local
src/App.vue
@@ -6,17 +6,50 @@
<script>
export default {
  name: 'App',
  data() {
    return {
      windowWidth: window.innerWidth,
      isCollapse: false
    }
  },
  watch: {
    windowWidth(newWidth) {
      // 当窗口宽度小于某个值时,可以触发折叠
      if (newWidth < 1200 && !this.isCollapse) {
        this.isCollapse = true
        this.$store.commit('SET_ISFOLD', true)
      } else if (newWidth >= 1200 && this.isCollapse) {
        this.isCollapse = false
        this.$store.commit('SET_ISFOLD', false)
      }
    }
  },
  created() {
    // 初始化时检查窗口大小
    this.handleResize()
  },
  mounted() {
    // 监听窗口大小变化
    window.addEventListener('resize', this.handleResize)
  },
  beforeDestroy() {
    // 移除监听
    window.removeEventListener('resize', this.handleResize)
  },
  methods: {
    handleResize() {
      this.windowWidth = window.innerWidth
    }
  },
}
</script>
<style>
<style lang="less">
::-webkit-scrollbar {
  display: none;
}
html,
body,
#app {
@@ -25,4 +58,15 @@
  padding: 0;
  background-color: rgb(245, 245, 245);
}
.selected {
  color: #049C9A !important;
}
.el-button--primary {
  background-color: #009688 !important;
  border-color: #009688 !important;
}
.el-button--text {
  color: #009688 !important;
}
</style>
src/assets/public/close-l@2x.png
src/assets/public/layoutsBG.png
src/assets/public/logOut.png
src/assets/public/notice@2x.png
src/assets/public/photo.png
src/components/Card/index.vue
New file
@@ -0,0 +1,23 @@
<template>
    <div class="card">
        <slot></slot>
    </div>
</template>
<script>
export default {
}
</script>
<style lang="less" scoped>
.card {
    height: calc(100% - 110px);
    overflow-y: auto;
    padding: 30px 30px 59px 30px;
    box-shadow: 0px 10px 19px 0px rgba(0, 0, 0, 0.06);
    border-radius: 16px;
    border: 4px solid #FFFFFF;
    background: rgba(255, 255, 255, 0.8);
}
</style>
src/components/Table/index.vue
New file
@@ -0,0 +1,125 @@
<template>
    <div class="table-container">
        <el-table border :data="tableData" :height="height">
            <slot></slot>
        </el-table>
        <div v-if="total > 0">
            <el-pagination layout="slot, prev, pager, next, sizes, jumper" :page-size="queryForm.pageSize"
                :current-page="queryForm.pageNum" :total="total" @current-change="handleCurrentChange"
                @size-change="handleSizeChange" class="pagination">
                <div class="pagination-info">第 {{ (queryForm.pageNum == 1) ? 1 : (queryForm.pageNum - 1) *
                    queryForm.pageSize + 1 }}-{{
                        queryForm.pageNum * queryForm.pageSize }} 条/总共 {{ total }} 条</div>
            </el-pagination>
        </div>
    </div>
</template>
<script>
export default {
    props: {
        tableData: {
            type: Array,
            default: () => []
        },
        total: {
            type: Number,
            default: 20
        },
        queryForm: {
            type: Object,
            default: () => {
                return {
                    pageSize: 10,
                    pageNum: 1
                }
            }
        }
    },
    computed: {
        height() {
            return this.$baseTableHeight()
        },
    },
    methods: {
        handleCurrentChange(page) {
            this.$emit('handleCurrentChange', page)
        },
        handleSizeChange(size) {
            this.$emit('handleSizeChange', size)
        }
    }
}
</script>
<style scoped lang="less">
.el-table--border,
.el-table--group {
    border-radius: 8px 8px 0px 0px;
    ::v-deep thead {
        tr {
            th {
                background: #FAFAFA !important;
            }
        }
    }
}
.pagination {
    display: flex;
    justify-content: flex-end;
    margin-top: 16px;
    .pagination-info {
        font-weight: 400;
        font-size: 14px;
        color: rgba(0, 0, 0, 0.88);
        line-height: 24px;
    }
    ::v-deep .el-pager li {
        padding: 0 !important;
        min-width: 24px !important;
        height: 24px !important;
        line-height: 24px !important;
        &:hover {
            color: #049C9A !important;
        }
    }
    ::v-deep .el-pager .active {
        color: #049C9A !important;
        border-radius: 6px !important;
        border: 1px solid #049C9A !important;
    }
    ::v-deep .el-pagination__jump {
        margin-left: 0 !important;
        .el-input__inner:focus {
            border: 1px solid #049C9A !important;
        }
    }
    ::v-deep .el-pagination__sizes .el-input .el-input__inner {
        color: #049C9A !important;
        border-color: #049C9A !important;
        &:hover {
            color: #049C9A !important;
            border-color: #049C9A !important;
        }
        &:focus {
            color: #049C9A !important;
            border-color: #049C9A !important;
        }
    }
    ::v-deep button:hover {
        color: #049C9A !important;
    }
}
</style>
src/components/TableSlot/index.vue
New file
@@ -0,0 +1,74 @@
<template>
    <div class="table-slot">
        <div class="search">
            <slot name="search"></slot>
        </div>
        <div class="table">
            <slot name="setting"></slot>
            <Table :tableData="tableData" :total="total" :queryForm="queryForm" @currentChange="handleCurrentChange" @sizeChange="handleSizeChange">
                <slot name="table"></slot>
            </Table>
        </div>
    </div>
</template>
<script>
import Table from '../Table/index.vue'
export default {
    components: {
        Table,
    },
    props: {
        tableData: {
            type: Array,
            default: () => []
        },
        total: {
            type: Number,
            default: 0
        },
        queryForm: {
            type: Object,
            default: () => {
                return {
                    pageSize: 10,
                    pageNum: 1
                }
            }
        }
    },
    methods: {
        handleCurrentChange(page) {
            this.$emit('handleCurrentChange', page)
        },
        handleSizeChange(size) {
            this.$emit('handleSizeChange', size)
        }
    }
}
</script>
<style scoped lang="less">
.table-slot {
    height: 100%;
    display: flex;
    flex-direction: column;
}
.search {
    padding: 34px 30px 15px 30px;
    box-shadow: 0px 10px 19px 0px rgba(0, 0, 0, 0.06);
    border-radius: 16px;
    border: 4px solid #FFFFFF;
    background: rgba(255, 255, 255, 0.8);
    margin-bottom: 30px;
}
.table {
    flex: 1;
    padding: 20px;
    background: rgba(255, 255, 255, 0.8);
    box-shadow: 0px 10px 19px 0px rgba(0, 0, 0, 0.06);
    border-radius: 16px;
    border: 4px solid #FFFFFF;
}
</style>
src/components/showDelConfirm/index.vue
@@ -4,7 +4,7 @@
            :close-on-click-modal="false" width="433px">
            <div class="top-con a-center" slot="title">
                <div class="left">
                    <img src="@/assets/notice@2x.png" style="width: 24px;height: 24px;margin-right: 14px;" />
                    <img src="@/assets/public/notice@2x.png" style="width: 24px;height: 24px;margin-right: 14px;" />
                    <div class="title">{{ title }}</div>
                </div>
            </div>
src/layouts/components/AppContent.vue
@@ -1,5 +1,21 @@
<template>
  <div>
  <div style="height: 100%;">
    <keep-alive :include="keepAliveList">
    <router-view />
    </keep-alive>
  </div>
</template>
<script>
import { mapState } from 'vuex'
export default {
  name: 'AppContent',
  data() {
    return {
    }
  },
  computed: {
    ...mapState(['keepAliveList'])
  }
}
</script>
src/layouts/components/ElMenu/MenuItem.vue
@@ -1,20 +1,20 @@
<template>
  <!-- 判断当前页面是否显示,如果hide为true,则不渲染该菜单 -->
  <div v-if="!item.meta.hide && menus.includes(item.meta.privilege)">
  <!-- <div v-if="!item.meta.hide"> -->
  <!-- <div v-if="!item.meta.hide && menus.includes(item.meta.privilege)"> -->
  <div v-if="!(item.meta && item.meta.hide)">
    <!-- 根菜单 -->
    <MenuLink :to="resolvePath()" v-if="!item.children">
      <el-menu-item :index="resolvePath()">
        <i :class="item.meta.icon || ''"></i>
        <span slot="title">{{ item.meta.title }}</span>
        <i :class="(item.meta && item.meta.icon) || ''"></i>
        <span slot="title">{{ (item.meta && item.meta.title) || '' }}</span>
      </el-menu-item>
    </MenuLink>
    <!-- 可展开菜单 -->
    <el-submenu :index="resolvePath()" v-else>
      <template slot="title">
        <i :class="item.meta.icon"></i>
        <span slot="title">{{ item.meta.title }}</span>
        <i :class="(item.meta && item.meta.icon) || ''"></i>
        <span slot="title">{{ (item.meta && item.meta.title) || '' }}</span>
      </template>
      <!-- 这里递归去展示多级菜单 -->
      <menu-item v-for="(route, index) in item.children" :key="index" :item="route"
@@ -77,13 +77,22 @@
};
</script>
<style lang="less" scoped>
.is-active {
  background-color: rgb(245, 245, 245);
::v-deep .router-link-exact-active .is-active {
  background: #EFF8FA;
  border-radius: 8px;
  font-weight: bold;
  color: #000;
  color: #05908E;
}
.el-menu {
  border-right: unset !important;
::v-deep .el-menu-item,
::v-deep .el-submenu__title {
  border-radius: 8px;
  height: 40px;
  line-height: 40px;
}
::v-deep .el-menu-item:hover,
::v-deep .el-submenu__title:hover {
  background: #EFF8FA;
}
</style>
src/layouts/components/ElMenu/index.vue
@@ -28,9 +28,9 @@
    };
  },
  mounted() {
    // 获取所有定义的一级菜单和多级菜单
    this.routersList = routers.options.routes[0].children;
    // 过滤掉登录路由,只获取主布局下的路由
    this.routersList = routers.options.routes.find(route => route.path === '/').children;
  },
};
</script>
src/layouts/components/HeaderNav.vue
@@ -2,45 +2,77 @@
  <div>
    <!-- 右侧用户登录图标 -->
    <div class="user-logininfo">
      <el-dropdown @command="clickmenu">
        <span class="el-dropdown-link right-userName">
          <div style="margin-left: 10px;">{{ userInfo.nickName }}</div>
        </span>
        <el-dropdown-menu slot="dropdown">
          <el-dropdown-item command="outlogin">退出登录</el-dropdown-item>
        </el-dropdown-menu>
      </el-dropdown>
      <div class="user-logininfo-icon">
        <!-- 折叠 -->
        <i @click="clickFold" class="el-icon-s-fold"></i>
        <!-- 标签列表 -->
        <div class="tag-list-container">
          <div class="tag-list" v-for="tag in tagList" :key="tag.name">
            <div @click="goTag(tag)" :class="{ 'activeTag': tag.path === $route.path }">
              {{ tag.meta.title }}
            </div>
            <i @click="closeTag(tag)" v-if="tagList.length > 1" class="el-icon-close"></i>
          </div>
        </div>
      </div>
      <div class="user-info">
        <img src="@/assets/public/photo.png" />
        <div class="user-info-text">欢迎您,admin</div>
        <div class="user-info-line"></div>
        <div @click="outLogin" class="user-info-out">
          <img src="@/assets/public/logOut.png" />
          <div class="user-info-out-text">退出登录</div>
        </div>
      </div>
    </div>
  </div>
</template>
<script>
import { mapState } from 'vuex'
export default {
  data() {
    return {
      userInfo: '',
    }
  },
  computed: {
    ...mapState(['tagList', 'isFold'])
  },
  mounted() {
    // 获取用户信息
    this.getUserInfo()
  },
  methods: {
    // 点击折叠按钮
    clickFold() {
      this.$store.commit('SET_ISFOLD', !this.isFold)
    },
    // 获取用户信息
    getUserInfo() {
      this.userInfo = JSON.parse(localStorage.getItem('userInfo'))
    },
    // 点击下拉菜单回调
    clickmenu(e) {
      if (e === 'outlogin') {
        this.outLogin()
      }
    },
    // 退出登录
    outLogin() {
      localStorage.clear()
      this.$router.replace({ path: "/" });
    },
    // 关闭标签
    closeTag(tag) {
      this.$store.commit('SET_TAGLIST', this.tagList.filter(item => item.path !== tag.path))
      // 判断是否是当前标签
      if (tag.path === this.$route.path) {
        // if (this.tagList.length > 1) {
        this.$router.push(this.tagList[this.tagList.length - 1].path)
        // } else {
        //   // this.$router.push('/welcome')
        // }
      }
    },
    // 跳转标签
    goTag(tag) {
      this.$router.push(tag.path)
    }
  },
}
</script>
@@ -49,16 +81,131 @@
// 右侧用户头像
.user-logininfo {
  height: 100%;
  padding-right: 40px;
  cursor: pointer;
  padding-left: 32px;
  padding-right: 20px;
  display: flex;
  align-items: center;
  justify-content: flex-end;
  justify-content: space-between;
  .user-logininfo-icon {
    flex: 1;
    flex-shrink: 0;
    display: flex;
    align-items: center;
    i:first-child {
      margin-right: 21px;
    }
    i {
      cursor: pointer;
    }
    .tag-list-container {
      display: flex;
      align-items: center;
      overflow-x: auto;
      .tag-list {
        flex-shrink: 0;
        display: flex;
        align-items: center;
        cursor: pointer;
        font-weight: 400;
        font-size: 18px;
        color: rgba(0, 0, 0, .6);
        margin-right: 40px;
        div:hover {
          font-weight: bold;
          color: #049C9A;
        }
        i {
          margin-left: 10px;
          &:hover {
            color: #049C9A;
          }
        }
      }
      .activeTag {
        font-weight: bold;
        color: #049C9A;
      }
    }
}
.right-userName {
  .user-info {
    flex-shrink: 0;
  display: flex;
  align-items: center;
    img {
      width: 26px;
      height: 26px;
      border-radius: 50%;
    }
    .user-info-text {
      margin-left: 16px;
      font-size: 14px;
      color: #303133;
      font-weight: 400;
    }
    .user-info-line {
      width: 1px;
      height: 12px;
      background-color: #979797;
      margin: 0 20px;
    }
    .user-info-out {
      display: flex;
      align-items: center;
      padding: 0 11px;
      border-radius: 12px;
      position: relative;
      background: transparent;
      cursor: pointer;
      &::before {
        content: '';
        position: absolute;
        top: 0;
        left: 0;
        right: 0;
        bottom: 0;
        border-radius: 12px;
        padding: 1px;
        background: linear-gradient(180deg, rgba(10, 203, 202, 1), rgba(4, 156, 154, 1));
        -webkit-mask:
          linear-gradient(#fff 0 0) content-box,
          linear-gradient(#fff 0 0);
        -webkit-mask-composite: xor;
        mask-composite: exclude;
      }
      .user-info-out-text {
        font-weight: 400;
        font-size: 14px;
        color: #049C9A;
        line-height: 21px;
        position: relative;
        z-index: 1;
      }
      img {
        width: 14px;
        height: 14px;
        margin-right: 7px;
        position: relative;
        z-index: 1;
      }
    }
  }
}
</style>
src/layouts/index.vue
@@ -3,23 +3,24 @@
    <!-- 判断是否在空白页打开 -->
    <template v-if="!isOneself">
      <div class="app-wrapper">
        <div class="left">
        <div class="left" :style="{ width: isFold ? '0px' : '258px' }">
          <!-- 系统标题 -->
          <div class="system-title">
          <div v-if="!isFold" class="system-title">
            <div class="image">
              <img src="../assets/logo.jpg" alt="" srcset="" />
            </div>
            <div class="title">职评网管理系统</div>
            <div class="title">实验室流程</div>
          </div>
          <!-- 左侧菜单 -->
          <div class="sidebar-container">
          <div v-if="!isFold" class="sidebar-container">
            <ElMenu />
          </div>
          <div v-if="!isFold" class="sidebar-left-bg"></div>
          <div v-if="!isFold" class="sidebar-bottom-bg"></div>
        </div>
        <!-- 右侧展示内容 -->
        <div class="main-container">
        <div class="main-container" :style="{ width: isFold ? '100%' : 'calc(100% - 258px)' }">
          <HeaderNav class="header-main" />
          <div v-if="nowRouteName" class="router_name">{{ nowRouteName }}</div>
          <AppContent class="app-main" />
        </div>
      </div>
@@ -35,6 +36,7 @@
import ElMenu from './components/ElMenu/index.vue'
import HeaderNav from './components/HeaderNav.vue'
import AppContent from './components/AppContent.vue'
import { mapState } from 'vuex'
export default {
  data() {
    return {
@@ -43,6 +45,9 @@
      // 获取当前页面名称
      nowRouteName: '',
    }
  },
  computed: {
    ...mapState(['isFold'])
  },
  components: {
    ElMenu,
@@ -77,66 +82,100 @@
  height: 100%;
  width: 100%;
  display: flex;
  background-image: url('../assets/public/layoutsBG.png');
  background-size: cover;
  background-position: center;
  .left {
    width: 200px;
    display: flex;
    flex-direction: column;
    position: relative;
    background-color: white;
    transition: width 0.3s ease-in-out;
    // 系统标题
    .system-title {
      overflow: hidden;
      white-space: nowrap;
      display: flex;
      justify-content: space-around;
      flex-direction: column;
      align-items: center;
      background-color: white;
      height: 50px;
      padding: 0px 10px;
      height: 240px;
      box-sizing: border-box;
      .image {
        width: 40px;
        height: 40px;
        margin-top: 40px;
        width: 100px;
        height: 100px;
        img {
          width: 100%;
          height: 100%;
          border-radius: 50%;
        }
      }
      .title {
        font-weight: 700;
        margin-top: 7px;
        font-weight: bold;
        line-height: 43px;
        font-size: 25px;
      }
    }
    // 左侧菜单
    .sidebar-container {
      z-index: 2;
      padding: 0 19px;
      overflow-y: auto;
      flex: 1;
      background-color: white;
      box-shadow: 0px 3px 13px 0px rgba(94, 131, 245, 0.1);
      ::v-deep .el-menu {
        border-right: unset !important;
      }
    }
    .sidebar-left-bg {
      z-index: 1;
      position: absolute;
      top: 338px;
      left: 0;
      width: 183px;
      height: 316px;
      background: #FFFCE5;
      opacity: 0.56;
      filter: blur(76.141290103688px);
    }
    .sidebar-bottom-bg {
      z-index: 1;
      position: absolute;
      bottom: 0;
      left: 0;
      width: 129px;
      height: 289px;
      background: #66FFFF;
      opacity: 0.4;
      filter: blur(76.141290103688px);
    }
  }
  .main-container {
    flex: 1;
    display: flex;
    flex-direction: column;
    transition: width 0.3s ease-in-out;
    .header-main {
      background-color: white;
      height: 50px;
    }
    .router_name {
      padding-top: 8px;
      padding-left: 23px;
      font-size: 24px;
      font-weight: bold;
      height: 70px;
    }
    .app-main {
      height: calc(100% - 120px);
      flex: 1;
      overflow: auto;
      border-radius: 10px;
      margin: 16px 23px;
      padding: 10px 21px 0 21px;
    }
  }
src/main.js
@@ -4,9 +4,15 @@
import App from "./App.vue";
import router from "./router";
import store from './store'
import TableCustom from '@/components/TableSlot/index.vue'
import Card from '@/components/Card/index.vue'
import ShowDelConfirm from '@/components/showDelConfirm/index.vue'
Vue.config.productionTip = false;
Vue.use(ElementUI, { size: 'small' })
Vue.component('TableCustom', TableCustom)
Vue.component('Card', Card)
Vue.component('ShowDelConfirm', ShowDelConfirm)
Vue.prototype.msgsuccess = function (msg) {
  this.$message({
@@ -33,6 +39,19 @@
  this.$message.info(msg);
}
Vue.prototype.$baseTableHeight = (formType) => {
  let height = window.innerHeight
  let paddingHeight = 400
  const formHeight = 50
  if ('number' == typeof formType) {
    height = height - paddingHeight - formHeight * formType
  } else {
    height = height - paddingHeight
  }
  return height
}
new Vue({
  router,
  store,
src/router/index.js
@@ -13,46 +13,86 @@
/**
 *  path: "/login",   ------页面地址
    component: () => import("../views/login"),  ------组件地址
    name: "Login",  ------组件名称 缓存时需要 唯一性
    meta: {
      title: "登录",    ------页面标题
      icon: "el-icon-user-solid",  ------菜单图标
      oneself: true,  ------是否在单独页面打开
      hide: true,  ------是否隐藏改菜单
      keepAlive: true,  ------是否缓存
    }
 */
const routes = [
    {
        path: "",
        redirect: "login",
        component: Layouts,
        children: [
            {
                path: "/login",
                meta: {
                    title: "登录",
                    oneself: true,
                    hide: true,
                    privilege: 'login'
                },
                component: () => import("../views/login"),
            },
        ],
    },{
        path: "",
        redirect: "dispatching",
    {
        path: "/",
        component: Layouts,
        children: [
            {
                path: "",
                redirect: "/projectList/list"
            },
            {
                path: "/projectList",
                meta: {
                    title: "项目组管理",
                },
                component: Parent,
                children: [
                    {
                        path: "list",
                        name: "ProjectList",
                        meta: {
                            title: "项目组管理",
                        },
                        component: () => import("../views/projectList"),
                    },
                    {
                        path: "addProject",
                        name: "AddProject",
                        meta: {
                            title: "新增项目组",
                            hide: true,
                            keepAlive: true,
                        },
                        component: () => import("../views/projectList/addProject"),
                    }
                ]
            },
            {
                path: "/dataManagement",
                component: Parent,
                meta: {
                    title: "实验室数据管理",
                },
                children: [
                    {
                        path: "/approvalPlan",
                        meta: {
                            title: "项目课题方案审批",
                            keepAlive: true,
                        },
                        component: () => import("../views/dataManagement/approvalPlan/list.vue"),
                    },
            {
                path: "/dispatching",
                meta: {
                    title: "实验调度管理",
                    oneself: true,
                    hide: true,
                    privilege: 'dispatching'
                            keepAlive: true,
                },
                component: () => import("../views/dispatching/list.vue"),
                        component: () => import("../views/dataManagement/dispatching/list.vue"),
            },
                ],
            }
        ],
    },
];
@@ -66,27 +106,53 @@
// 前置路由拦截器
router.beforeEach((to, from, next) => {
    // 设置当前页签名称
    document.title = to.meta.title || '职评网管理系统';
    // 没有登录并且要去的页面不是登录页面,在强制跳转到登录
    if (to.path === "/login") {
        localStorage.removeItem('userInfo')
        next()
    } else if (!localStorage.getItem('userInfo')) {
        next('/login')
    } else {
        // 判断是否拥有要跳转菜单权限
        let menus = store.state.menus
    document.title = to.meta.title || '实验室流程';
        // console.log(store.state.menus);
        // console.log(to.meta);
        // console.log(to.meta.hasOwnProperty('privilege'));
        // console.log(!menus.includes(to.meta.privilege));
    // 登录验证
    // if (to.path === "/login") {
    //     localStorage.removeItem('userInfo')
    //     next()
    // } else if (!localStorage.getItem('userInfo')) {
    //     next('/login')
    // } else {
    //     // 判断是否拥有要跳转菜单权限
    //     let menus = store.state.menus
    //     if (to.meta.hasOwnProperty('privilege') && !menus.includes(to.meta.privilege)) {
    //         return
    //     }
        
        if (to.meta.hasOwnProperty('privilege') && !menus.includes(to.meta.privilege)) {
            return
        // 设置标签列表
        if (!to.meta.hide || !to.meta.oneself) {
            let tagList = JSON.parse(localStorage.getItem('tagList') || '[]')
            // 判断是否存在
            let isExist = tagList.some(item => item.path === to.path)
            if (!isExist) {
                // 只保存必要的信息
                const tagInfo = {
                    path: to.path,
                    name: to.name,
                    meta: to.meta
        }
                tagList.push(tagInfo)
                localStorage.setItem('tagList', JSON.stringify(tagList))
                store.commit('SET_TAGLIST', tagList)
            }
        }
        // 判断是否需要缓存
        if (to.meta.keepAlive) {
            let keepAliveList = JSON.parse(localStorage.getItem('keepAliveList') || '[]')
            // 判断是否已经缓存
            let isExist = keepAliveList.includes(to.name)
            if (!isExist) {
                keepAliveList.push(to.name)
                localStorage.setItem('keepAliveList', JSON.stringify(keepAliveList))
                store.commit('SET_KEEPALIVELIST', keepAliveList)
            }
        }
        next()
    }
    // }
});
export default router;
src/store/index.js
@@ -5,15 +5,38 @@
const store = new Vuex.Store({
  state: {
    menus: localStorage.getItem('menuList') ? JSON.parse(localStorage.getItem('menuList')) : [],
    keepAliveList: localStorage.getItem('keepAliveList') ? JSON.parse(localStorage.getItem('keepAliveList')) : [],//缓存页面
    tagList: localStorage.getItem('tagList') ? JSON.parse(localStorage.getItem('tagList')) : [],//标签列表
    isFold: false,//是否折叠
  },
  mutations: {
    SET_MENUS(state, data) {
      state.menus = data;
    },
    SET_KEEPALIVELIST(state, data) {
      state.keepAliveList = data;
      localStorage.setItem('keepAliveList', JSON.stringify(data));
    },
    SET_TAGLIST(state, data) {
      state.tagList = data;
      localStorage.setItem('tagList', JSON.stringify(data));
    },
    SET_ISFOLD(state, data) {
      state.isFold = data;
    },
  },
  actions: {
    setMenus({ commit }, data) {
      commit('SET_MENUS', data);
    },
    setKeepAliveList({ commit }, data) {
      commit('SET_KEEPALIVELIST', data);
    },
    setTagList({ commit }, data) {
      commit('SET_TAGLIST', data);
    },
    setIsFold({ commit }, data) {
      commit('SET_ISFOLD', data);
    }
  }
})
src/views/confirmation-sheet/index.vue
New file
@@ -0,0 +1,100 @@
<template>
    <div class="list">
        <TableCustom :queryForm="queryForm" :total="total" @currentChange="handleCurrentChange" @sizeChange="handleSizeChange">
            <template #search>
                <el-form :model="form" label-width="140px" inline>
                    <el-form-item label="项目组名称:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="项目负责人:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="通创建日期:">
                        <el-input v-model="form.name"></el-input>
                    </el-form-item>
                    <el-form-item label="" style="margin-left: 63px;">
                        <el-button type="default">重置</el-button>
                        <el-button type="primary">查询</el-button>
                    </el-form-item>
                </el-form>
            </template>
            <template #setting>
                <el-button class="el-icon-plus" style="margin-bottom: 20px;" type="primary"> 新增项目组</el-button>
            </template>
            <template #table>
                <el-table-column prop="name" label="项目组名称" />
                <el-table-column prop="age" label="项目负责人" />
                <el-table-column prop="age" label="项目组成员" />
                <el-table-column prop="age" label="项目创建时间" />
                <el-table-column prop="age" 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="操作">
                    <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 type="text" @click="handleDel(row)">删除</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <ShowDelConfirm :show="showDelConfirm" @close="showDelConfirm = false" @confirm="handleDelConfirm" />
        <ShowDelConfirm :title="changeStatusTitle" :tip="changeStatusTip" :show="changeStatus" @close="changeStatus = false" @confirm="handleChangeStatus" />
    </div>
</template>
<script>
export default {
    name: 'ProjectList',
    data() {
        return {
            form: {
                name: ''
            },
            showDelConfirm: false,
            rowId: '',
            changeStatus: false,
            changeStatusTitle: '',
            changeStatusTip: '',
            queryForm: {
                pageSize: 10,
                pageNum: 1
            },
            total: 0
        }
    },
    methods: {
        handleDel(row) {
            this.showDelConfirm = true
            this.rowId = row.id
        },
        handleDelConfirm() {
            this.showDelConfirm = false
            this.msgsuccess('删除成功')
        },
        handleChangeStatus(row, status) {
            this.changeStatus = true
            this.rowId = row.id
            this.changeStatusTitle = status == 1 ? '确认要封存这个项目组吗?' : '确认要解封该项目组吗?'
            this.changeStatusTip = status == 1 ? '封存后项目组内人员看不到数据,审批人仍然可见数据。' : '解封后项目组内人员数据恢复。'
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
        },
        handleSizeChange(size) {
            this.queryForm.pageSize = size
        }
    }
}
</script>
<style scoped lang="less">
.list {
    height: 100%;
}
</style>
src/views/dataManagement/approvalPlan/list.vue
New file
@@ -0,0 +1,152 @@
<template>
  <div class="list">
    <TableCustom :queryForm="form" :tableData="tableData" :total="total">
      <template #search>
        <el-form :model="form" label-width="140px" inline>
          <el-form-item label="项目课题方案名称:">
            <el-input v-model="form.planName" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="项目课题方案编号:">
            <el-input v-model="form.planCode" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="创建人:">
            <el-input v-model="form.creator" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="创建时间:">
            <el-date-picker
              v-model="form.createTime"
              type="daterange"
              range-separator="至"
              start-placeholder="开始日期"
              end-placeholder="结束日期"
              value-format="yyyy-MM-dd"
            ></el-date-picker>
          </el-form-item>
          <el-form-item label="审批人:">
            <el-input v-model="form.approver" placeholder="请输入"></el-input>
          </el-form-item>
          <el-form-item label="">
            <el-button type="default" @click="resetForm">重置</el-button>
            <el-button type="primary" @click="handleSearch">查询</el-button>
          </el-form-item>
        </el-form>
      </template>
      <template #setting>
        <div class="tableTitle">
          <div class="title">项目课题方案列表</div>
        </div>
      </template>
      <template #table>
          <el-table-column prop="planCode" label="项目课题方案编号"></el-table-column>
          <el-table-column prop="planName" label="项目课题方案名称"></el-table-column>
          <el-table-column prop="stage" label="项目阶段"></el-table-column>
          <el-table-column prop="creator" label="创建人"></el-table-column>
          <el-table-column prop="createTime" label="创建日期"></el-table-column>
          <el-table-column prop="status" label="审批状态">
            <template slot-scope="scope">
              <el-tag :type="getStatusType(scope.row.status)">
                {{ getStatusText(scope.row.status) }}
              </el-tag>
            </template>
          </el-table-column>
          <el-table-column prop="approver" label="审批人"></el-table-column>
          <el-table-column prop="approveTime" label="审批时间"></el-table-column>
          <el-table-column label="操作" width="150">
            <template slot-scope="scope">
              <el-button
                v-if="scope.row.status === 'pending'"
                type="text"
                @click="handleApprove(scope.row)"
              >审批</el-button>
              <el-button
                type="text"
                @click="handleDetail(scope.row)"
              >详情</el-button>
            </template>
          </el-table-column>
      </template>
    </TableCustom>
  </div>
</template>
<script>
export default {
  name: "ProjectList",
  data() {
    return {
      form: {
        planName: "",
        planCode: "",
        creator: "",
        createTime: [],
        approver: "",
        status: ""
      },
      tableData: [],
      total: 0
    };
  },
  methods: {
    resetForm() {
      this.form = {
        planName: "",
        planCode: "",
        creator: "",
        createTime: [],
        approver: "",
        status: ""
      };
    },
    handleSearch() {
      // 实现查询逻辑
      console.log('查询条件:', this.form);
    },
    getStatusType(status) {
      const statusMap = {
        pending: 'warning',
        rejected: 'danger',
        approved: 'success',
        archived: 'info'
      };
      return statusMap[status] || 'info';
    },
    getStatusText(status) {
      const statusMap = {
        pending: '待审批',
        rejected: '已驳回',
        approved: '已通过',
        archived: '已封存'
      };
      return statusMap[status] || '未知';
    },
    handleApprove(row) {
      // 实现审批逻辑
      console.log('审批数据:', row);
    },
    handleDetail(row) {
      // 实现查看详情逻辑
      console.log('查看详情:', row);
    }
  }
};
</script>
<style scoped lang="less">
.list {
  height: 100%;
}
.tableTitle {
  display: flex;
  padding-bottom: 20px;
  .title {
    background: #ffffff;
    border-radius: 8px 8px 0px 0px;
    border: 1px solid #049c9a;
    padding: 16px 29px;
    font-weight: bold;
    font-size: 18px;
    color: #049c9a;
    width: unset;
  }
}
</style>
src/views/dataManagement/dispatching/list.vue
src/views/projectList/addProject.vue
New file
@@ -0,0 +1,19 @@
<template>
    <Card>
        <template>
        </template>
    </Card>
</template>
<script>
export default {
    name: 'AddProject',
    data() {
        return {
        }
    }
}
</script>
<style></style>
src/views/projectList/index.vue
New file
@@ -0,0 +1,123 @@
<template>
    <div class="list">
        <TableCustom :queryForm="queryForm" :total="total" @currentChange="handleCurrentChange"
            @sizeChange="handleSizeChange">
            <template #search>
                <el-form :model="form" label-width="140px" inline>
                    <el-form-item label="项目组名称:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="项目负责人:">
                        <el-input v-model="form.name" placeholder="请输入"></el-input>
                    </el-form-item>
                    <el-form-item label="通创建日期:">
                        <el-input v-model="form.name"></el-input>
                    </el-form-item>
                    <el-form-item label="" style="margin-left: 63px;">
                        <el-button type="default">重置</el-button>
                        <el-button type="primary">查询</el-button>
                    </el-form-item>
                </el-form>
            </template>
            <template #setting>
                <el-button @click="handleAddProject" class="el-icon-plus" style="margin-bottom: 20px;" type="primary">
                    新增项目组</el-button>
            </template>
            <template #table>
                <el-table-column prop="name" label="项目组名称" />
                <el-table-column prop="age" label="项目负责人" />
                <el-table-column prop="age" label="项目组成员" />
                <el-table-column prop="age" label="项目创建时间" />
                <el-table-column prop="age" 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="操作">
                    <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 type="text" @click="handleDel(row)">删除</el-button>
                    </template>
                </el-table-column>
            </template>
        </TableCustom>
        <ShowDelConfirm :show="showDelConfirm" @close="showDelConfirm = false" @confirm="handleDelConfirm" />
        <ShowDelConfirm :title="changeStatusTitle" :tip="changeStatusTip" :show="changeStatus"
            @close="changeStatus = false" @confirm="handleChangeStatusConfirm" />
    </div>
</template>
<script>
export default {
    name: 'ProjectList',
    data() {
        return {
            form: {
                name: ''
            },
            showDelConfirm: false,
            rowId: '',
            changeStatus: false,
            changeStatusTitle: '',
            changeStatusTip: '',
            queryForm: {
                pageSize: 10,
                pageNum: 1
            },
            total: 0
        }
    },
    methods: {
        handleAddProject() {
            this.$router.push({
                path: '/projectList/addProject'
            })
        },
        handleDel(row) {
            this.rowId = row.id
            this.showDelConfirm = true
        },
        handleDelConfirm() {
            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.changeStatus = true
        },
        handleChangeStatusConfirm() {
            this.changeStatus = false
            this.msgsuccess('操作成功')
            this.rowId = ''
            this.changeStatusTitle = ''
            this.changeStatusTip = ''
            this.getList()
        },
        handleCurrentChange(page) {
            this.queryForm.pageNum = page
            this.getList()
        },
        handleSizeChange(size) {
            this.queryForm.pageSize = size
            this.getList()
        },
        getList() {
        }
    }
}
</script>
<style scoped lang="less">
.list {
    height: 100%;
}
</style>