testvue-common

1bus.js

javascript 复制代码
import Vue from 'vue';

// 使用 Event Bus
const bus = new Vue();

export default bus;

2directives.js

javascript 复制代码
import Vue from 'vue';

// v-dialogDrag: 弹窗拖拽属性
Vue.directive('dialogDrag', {
    bind(el, binding, vnode, oldVnode) {
        const dialogHeaderEl = el.querySelector('.el-dialog__header');
        const dragDom = el.querySelector('.el-dialog');

        dialogHeaderEl.style.cssText += ';cursor:move;'
        dragDom.style.cssText += ';top:0px;'

        // 获取原有属性 ie dom元素.currentStyle 火狐谷歌 window.getComputedStyle(dom元素, null);
        const sty = (() => {
            if (window.document.currentStyle) {
                return (dom, attr) => dom.currentStyle[attr];
            } else {
                return (dom, attr) => getComputedStyle(dom, false)[attr];
            }
        })()

        dialogHeaderEl.onmousedown = (e) => {
            // 鼠标按下,计算当前元素距离可视区的距离
            const disX = e.clientX - dialogHeaderEl.offsetLeft;
            const disY = e.clientY - dialogHeaderEl.offsetTop;

            const screenWidth = document.body.clientWidth; // body当前宽度
            const screenHeight = document.documentElement.clientHeight; // 可见区域高度(应为body高度,可某些环境下无法获取) 

            const dragDomWidth = dragDom.offsetWidth; // 对话框宽度
            const dragDomheight = dragDom.offsetHeight; // 对话框高度

            const minDragDomLeft = dragDom.offsetLeft;
            const maxDragDomLeft = screenWidth - dragDom.offsetLeft - dragDomWidth;

            const minDragDomTop = dragDom.offsetTop;
            const maxDragDomTop = screenHeight - dragDom.offsetTop - dragDomheight;


            // 获取到的值带px 正则匹配替换
            let styL = sty(dragDom, 'left');
            let styT = sty(dragDom, 'top');

            // 注意在ie中 第一次获取到的值为组件自带50% 移动之后赋值为px
            if (styL.includes('%')) {
                styL = +document.body.clientWidth * (+styL.replace(/\%/g, '') / 100);
                styT = +document.body.clientHeight * (+styT.replace(/\%/g, '') / 100);
            } else {
                styL = +styL.replace(/\px/g, '');
                styT = +styT.replace(/\px/g, '');
            };

            document.onmousemove = function (e) {
                // 通过事件委托,计算移动的距离 
                let left = e.clientX - disX;
                let top = e.clientY - disY;

                // 边界处理
                if (-(left) > minDragDomLeft) {
                    left = -(minDragDomLeft);
                } else if (left > maxDragDomLeft) {
                    left = maxDragDomLeft;
                }

                if (-(top) > minDragDomTop) {
                    top = -(minDragDomTop);
                } else if (top > maxDragDomTop) {
                    top = maxDragDomTop;
                }

                // 移动当前元素  
                dragDom.style.cssText += `;left:${left + styL}px;top:${top + styT}px;`;
            };

            document.onmouseup = function (e) {
                document.onmousemove = null;
                document.onmouseup = null;
            };
        }
    }
})

3header.vue

html 复制代码
<template>
    <div class="header">
        <!-- 折叠按钮 -->
        <div class="collapse-btn" @click="collapseChage">
            <i v-if="!collapse" class="el-icon-s-fold"></i>
            <i v-else class="el-icon-s-unfold"></i>
        </div>
        <div class="logo">在线考试管理系统</div>
        <div class="header-right">
            <div class="header-user-con">
                <!-- 全屏显示 -->
                <div class="btn-fullscreen" @click="handleFullScreen">
                    <el-tooltip effect="dark" :content="fullscreen?`取消全屏`:`全屏`" placement="bottom">
                        <i class="el-icon-rank"></i>
                    </el-tooltip>
                </div>
                <!-- 用户头像 -->
                <div class="user-avator">
                    <img :src="getAvatar(form.avatar)" alt="User Avatar">
                </div>
                <!-- 用户名下拉菜单 -->
                <el-dropdown class="user-name" trigger="click" @command="handleCommand">
                    <span class="el-dropdown-link">
                        {{username}}
                        <i class="el-icon-caret-bottom"></i>
                    </span>
                    <el-dropdown-menu slot="dropdown">
                        <el-dropdown-item command="updatePassword">个人中心</el-dropdown-item>
                        <el-button @click="openDialog">待办事件</el-button>
                        <el-dropdown-item command="loginout">退出登录</el-dropdown-item>
                    </el-dropdown-menu>
                </el-dropdown>
            </div>
        </div>
        <el-dialog title="个人中心&&信息查看的同时修改密码&&修改头像" :modal="false"  :visible.sync="addVisible" width="33%" :before-close="clearAddForm">
            <el-form label-position="left"  ref="addForm" label-width="80px"  :model="form" >
                <el-form-item label="用户名">
                    <el-input v-model="form.userName"></el-input>
                </el-form-item>
                <el-form-item label="密码">
                    <el-input v-model="form.password"></el-input>
                </el-form-item>
                <el-form-item label="真实姓名">
                    <el-input v-model="form.realName"></el-input>
                </el-form-item>
                <el-form-item label="手机号">
                    <el-input v-model="form.phone"></el-input>
                </el-form-item>
            </el-form>
            <span slot="footer" class="dialog-footer">
                <el-button @click="clearAddForm">取 消</el-button>
                <el-button type="primary" @click="saveAndUpdateUserInfo">确 定</el-button>
            </span>
        </el-dialog>

        <el-dialog title="待办事件" :visible.sync="addVisible2" width="72%" style="margin-left: 155px;" :before-close="clearAddForm">
            <div v-for="(todo, index) in todoList" :key="index" class="todo-item">
                <el-input v-model="todo.theme" placeholder="事件标题" style="width: 18%; margin-left: 42px;"></el-input>
                
                <el-input type="textarea" v-model="todo.content" placeholder="事件详情" rows="1" style="width: 40%;"></el-input>
                <el-input
                    style="width: 9%;"
                    v-model="todo.remainDay"
                    type="number"
                    placeholder="天数"
                ></el-input>
                <el-input v-model="todo.stopTimeStr" placeholder="截止日期" style="width: 14%;" disabled></el-input>
                <el-select v-model="todo.status" placeholder="状态" style="width: 10%;">
                    <el-option label="待办" value="0"></el-option>
                    <el-option label="进行中" value="1"></el-option>
                    <el-option label="完成" value="2"></el-option>
                </el-select>

                <el-button style="margin-left: 30px;" type="danger" icon="el-icon-delete" @click="removeTodo(index)">删除</el-button>
            </div>
            <el-button type="primary" icon="el-icon-plus" @click="addTodo">新增待办事件</el-button>
            <span slot="footer" class="dialog-footer">
                <el-button @click="clearAddForm">取消</el-button>
                <el-button type="primary" @click="saveTodos">保存</el-button>
            </span>
    </el-dialog>


    </div>
</template>
<script>
import bus from '../common/bus';
import { getNoticeList, getUserList, saveAndUpdateEmployeeInfo, saveAndUpdateUserInfo } from '../../api/user';
import { getUpcomeList, addUpcome, updateUpcome, deleteUpcome,saveUpcome } from '@/api/upcome';
export default {
    data() {
        return {
            form:{
                id:sessionStorage.getItem("userId"),
                
            },
            userId:sessionStorage.getItem("userId"),
            addVisible:false,
            collapse: false,
            addVisible2:false,
            todoList: [],
            fullscreen: false,
            name: 'wahah',
            systemTitle:'',
            list:[],
            
        };
    },
    computed: {
        username() {
            let username = sessionStorage.getItem('realName');
            return username ? username : this.name;
        },
    },

    created() {
        this.getUserInfo();
    },
    
    methods: {
        fetchUpcomeList() {
            const userId = sessionStorage.getItem("userId");
            getUpcomeList(userId).then(response => {
                this.todoList = response.data.list; // 直接使用后端返回的数据
            });
        },

        openDialog() {
            this.addVisible2 = true;
            this.fetchUpcomeList();
        },

        // 添加待办事件
        addTodo() {
            const userId = sessionStorage.getItem("userId");
            this.todoList.push({ userId: userId, theme: "", stop_time: new Date().toLocaleString('en-US', { hour12: false }), content: "" });
        },

        // 删除待办事件
        removeTodo(index) {
            const todo = this.todoList[index];
            if (todo.id) {
                // 假设您有一个变量 `userId` 存储当前用户的ID
                const payload = { id: todo.id, userId: this.userId };
                deleteUpcome(payload).then(() => {
                    this.todoList.splice(index, 1);
                });
            } else {
                this.todoList.splice(index, 1);
            }
        },


        // 保存待办事件列表
        saveTodos() {
            const userId = sessionStorage.getItem("userId");
            Promise.all(this.todoList.map(todo => saveUpcome({ ...todo, userId: userId }))).then(() => {
                console.log("保存的待办事件列表:", this.todoList);
                this.clearAddForm();
            });
        },





        getAvatar(avatarFilename) {
            // 确保路径正确,并且文件确实存在于 src/assets/img 目录中
            return 'http://localhost:9000/img/' + avatarFilename;
    },
       
        getUserInfo(){
            getUserList({id:sessionStorage.getItem("userId")}).then(res =>{
                this.form = res.data.list[0];
                console.log(this.form)
            });
        },

        // 用户名下拉菜单选择事件
        handleCommand(command) {
            if (command === 'loginout') {
               // localStorage.removeItem('ms_username');
                sessionStorage.clear();
                window.location.reload();
                this.$router.push('/login');
            }
            if (command === 'updatePassword'){
                this.addVisible = true;
            }
            if (command === 'myNotice'){
                this.addVisible2 = true;
            }
        },

        clearAddForm(){
            this.addVisible = false;
            this.addVisible2 = false;
        },

        saveAndUpdateUserInfo(){
            saveAndUpdateUserInfo(this.form).then(res =>{
                if (res.code === 1){
                    this.$message.success('修改成功,即将跳转登录界面');
                    this.addVisible = false;
                    setTimeout(() =>{
                        this.$router.push('/login');
                    },2000)

                }else {
                    this.$message.error('修改失败')
                }
            })
        },

        // 侧边栏折叠
        collapseChage() {
            this.collapse = !this.collapse;
            bus.$emit('collapse', this.collapse);
        },

        // 全屏事件
        handleFullScreen() {
            let element = document.documentElement;
            if (this.fullscreen) {
                if (document.exitFullscreen) {
                    document.exitFullscreen();
                } else if (document.webkitCancelFullScreen) {
                    document.webkitCancelFullScreen();
                } else if (document.mozCancelFullScreen) {
                    document.mozCancelFullScreen();
                } else if (document.msExitFullscreen) {
                    document.msExitFullscreen();
                }
            } else {
                if (element.requestFullscreen) {
                    element.requestFullscreen();
                } else if (element.webkitRequestFullScreen) {
                    element.webkitRequestFullScreen();
                } else if (element.mozRequestFullScreen) {
                    element.mozRequestFullScreen();
                } else if (element.msRequestFullscreen) {
                    // IE11
                    element.msRequestFullscreen();
                }
            }
            this.fullscreen = !this.fullscreen;
        },


    },
    filters: {
        formatDate(date) {
            const options = { year: 'numeric', month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit', second: '2-digit' };
            return new Date(date).toLocaleDateString('en-US', options);
        }
    },
    mounted() {
        const that = this;
        if (document.body.clientWidth < 1500) {
            this.collapseChage();
        }
    },

};
</script>
<style scoped>
.todo-item {
    margin-bottom: 10px;
    display: flex;
    align-items: center;
    gap: 10px;
}

.header {
    position: relative;
    box-sizing: border-box;
    width: 100%;
    height: 70px;
    font-size: 22px;
    color: #fff;
}
.collapse-btn {
    float: left;
    padding: 0 21px;
    cursor: pointer;
    line-height: 70px;
}
.header .logo {
    float: left;
    width: 280px;
    line-height: 70px;
   
}
.header-right {
    float: right;
    padding-right: 50px;
}
.header-user-con {
    display: flex;
    height: 70px;
    align-items: center;
}
.btn-fullscreen {
    transform: rotate(45deg);
    margin-right: 5px;
    font-size: 24px;
}
.btn-bell,
.btn-fullscreen {
    position: relative;
    width: 30px;
    height: 30px;
    text-align: center;
    border-radius: 15px;
    cursor: pointer;
}
.btn-bell-badge {
    position: absolute;
    right: 0;
    top: -2px;
    width: 8px;
    height: 8px;
    border-radius: 4px;
    background: #f56c6c;
    color: #fff;
}
.btn-bell .el-icon-bell {
    color: #fff;
}
.user-name {
    margin-left: 10px;
}
.user-avator {
    margin-left: 20px;
}
.user-avator img {
    display: block;
    width: 40px;
    height: 40px;
    border-radius: 50%;
}
.el-dropdown-link {
    color: #fff;
    cursor: pointer;
}
.el-dropdown-menu__item {
    text-align: center;
}
</style>

4home.vue

html 复制代码
<template>
    <div class="wrapper">
        <v-head></v-head>
        <v-sidebar></v-sidebar>
        <div class="content-box" :class="{'content-collapse':collapse}">
            <v-tags></v-tags>
            <div class="content">
                <transition name="move" mode="out-in">
                    <keep-alive :include="tagsList">
                        <router-view></router-view>
                    </keep-alive>
                </transition>
                <el-backtop target=".content"></el-backtop>
            </div>
        </div>
    </div>
</template>

<script>
import vHead from './Header.vue';
import vSidebar from './Sidebar.vue';
import vTags from './Tags.vue';
import bus from './bus';
export default {
    data() {
        return {
            tagsList: [],
            collapse: false
        };
    },
    components: {
        vHead,
        vSidebar,
        vTags
    },
    created() {
        bus.$on('collapse-content', msg => {
            this.collapse = msg;
        });

        // 只有在标签页列表里的页面才使用keep-alive,即关闭标签之后就不保存到内存中了。
        bus.$on('tags', msg => {
            let arr = [];
            for (let i = 0, len = msg.length; i < len; i++) {
                msg[i].name && arr.push(msg[i].name);
            }
            this.tagsList = arr;
        });
    }
};
</script>

5i18m.js

javascript 复制代码
export const messages = {
    'zh': {
        i18n: {
            breadcrumb: '国际化产品',
            tips: '通过切换语言按钮,来改变当前内容的语言。',
            btn: '切换英文',
            title1: '常用用法',
            p1: '要是你把你的秘密告诉了风,那就别怪风把它带给树。',
            p2: '没有什么比信念更能支撑我们度过艰难的时光了。',
            p3: '只要能把自己的事做好,并让自己快乐,你就领先于大多数人了。',
            title2: '组件插值',
            info: 'Element组件需要国际化,请参考 {action}。',
            value: '文档'
        }
    },
    'en': {
        i18n: {
            breadcrumb: 'International Products',
            tips: 'Click on the button to change the current language. ',
            btn: 'Switch Chinese',
            title1: 'Common usage',
            p1: "If you reveal your secrets to the wind you should not blame the wind for  revealing them to the trees.",
            p2: "Nothing can help us endure dark times better than our faith. ",
            p3: "If you can do what you do best and be happy, you're further along in life  than most people.",
            title2: 'Component interpolation',
            info: 'The default language of Element is Chinese. If you wish to use another language, please refer to the {action}.',
            value: 'documentation'
        }
    }
}

6index.vue

html 复制代码
<template>
  <div class="app-container">

    <div class="filter-container">

      <slot name="filter-content" />

      <el-row>
        <el-col>
          <el-button v-if="options.addRoute" type="primary" icon="el-icon-plus" @click="handleAdd">添加</el-button>
        </el-col>
      </el-row>

    </div>

    <div v-show="multiShow && options.multiActions" class="filter-container">

      <el-select v-model="multiNow" :placeholder="selectedLabel" class="filter-item" style="width: 130px" @change="handleOption">
        <el-option
          v-for="item in options.multiActions"
          :key="item.value"
          :label="item.label"
          :value="item.value"
        />
      </el-select>

    </div>

    <el-table
      v-loading="listLoading"
      :data="dataList.records"
      border
      fit
      highlight-current-row
      :header-cell-style="{'background':'#f2f3f4', 'color':'#555', 'font-weight':'bold', 'line-height':'32px'}"
      @selection-change="handleSelection"
    >

      <el-table-column
        v-if="options.multi"
        align="center"
        type="selection"
        width="55"
      />

      <slot name="data-columns" />

    </el-table>

    <pagination v-show="dataList.total>0" :total="dataList.total" :page.sync="listQuery.current" :limit.sync="listQuery.size" @pagination="getList" />
  </div>
</template>

<script>
import { fetchList, deleteData, changeState } from '@/api/common'
import Pagination from '@/components/Pagination'

export default {
  name: 'PagingTable',
  components: { Pagination },
  // 组件入参
  props: {
    options: {
      type: Object,
      default: () => {
        return {
          // 批量操作
          multiActions: [],
          // 列表请求URL
          listUrl: '/exam/api',
          // 删除请求URL
          deleteUrl: '',
          // 启用禁用
          stateUrl: '',
          // 可批量操作
          multi: false
        }
      }
    },

    // 列表查询参数
    listQuery: {
      type: Object,
      default: () => {
        return {
          current: 1,
          size: 10,
          params: {},
          t: 0
        }
      }
    }
  },
  data() {
    return {
      // 接口数据返回
      dataList: {
        total: 0
      },
      // 数据加载标识
      listLoading: true,
      // 选定和批量操作
      selectedIds: [],
      selectedObjs: [],
      // 显示已中多少项
      selectedLabel: '',
      // 显示批量操作
      multiShow: false,
      // 批量操作的标识
      multiNow: ''
    }
  },
  watch: {

    // 检测查询变化
    listQuery: {
      handler() {
        this.getList()
      },
      deep: true
    }
  },
  created() {
    this.getList()
  },
  methods: {

    /**
     * 添加数据跳转
     */
    handleAdd() {
      if (this.options.addRoute) {
        this.$router.push({ name: this.options.addRoute, params: {}})
        return
      }
      console.log('未设置添加数据跳转路由!')
    },

    /**
     * 查询数据列表
     */
    getList() {
      this.listLoading = true
      this.listQuery.t = new Date().getTime()
      fetchList(this.options.listUrl, this.listQuery).then(response => {
        this.dataList = response.data
        this.listLoading = false
      })
    },

    /**
     * 搜索
     */
    handleFilter() {
      // 重新搜索
      this.getList()
    },

    /**
     * 批量操作回调
     */
    handleOption(v) {
      this.multiNow = ''

      // 内部消化的操作
      if (v === 'delete') {
        this.handleDelete()
        return
      }

      if (v === 'enable') {
        this.handleState(0)
        return
      }

      if (v === 'disable') {
        this.handleState(1)
        return
      }

      // 向外回调的操作
      this.$emit('multi-actions', { opt: v, ids: this.selectedIds })
    },

    /**
     * 修改状态,启用禁用
     */
    handleState(state) {
      // 修改状态
      changeState(this.options.stateUrl, this.selectedIds, state).then(response => {
        if (response.code === 0) {
          this.$message({
            type: 'success',
            message: '状态修改成功!'
          })

          // 重新搜索
          this.getList()
        }
      })
    },

    /**
     * 删除数据
     */
    handleDelete() {
      if (this.selectedIds.length === 0) {
        this.$message({
          message: '请至少选择一条数据!',
          type: 'warning'
        })
        return
      }

      // 删除
      this.$confirm('确实要删除吗?', '提示', {
        confirmButtonText: '确定',
        cancelButtonText: '取消',
        type: 'warning'
      }).then(() => {
        deleteData(this.options.deleteUrl, this.selectedIds).then(() => {
          this.$message({
            type: 'success',
            message: '删除成功!'
          })
          this.getList()
        })
      })
    },

    /**
     * 列表多选操作
     * @param val
     */
    handleSelection(val) {
      const ids = []
      val.forEach(row => {
        ids.push(row.id)
      })

      this.selectedObjs = val
      this.selectedIds = ids
      this.multiShow = ids.length > 0
      this.selectedLabel = '已选' + ids.length + '项'

      this.$emit('select-changed', { ids: this.selectedIds, objs: this.selectedObjs })
    }

  }
}
</script>

<style>

  .filter-container .filter-item{
    margin-left: 5px;
  }

  .filter-container .filter-item:first-child{
    margin-left: 0px;
  }
</style>

7sidebar.vue

html 复制代码
<template>
    <div class="sidebar">
        <el-menu
            class="sidebar-el-menu"
            :default-active="onRoutes"
            :collapse="collapse"
            background-color="#324157"
            text-color="#bfcbd9"
            active-text-color="#20a0ff"
            unique-opened
            router
        >
            <template v-for="item in items">
                <template v-if="item.subs">
<!--                    一级菜单-->
                    <el-submenu :index="item.index" :key="item.index">
                        <template slot="title">
                            <i :class="item.icon"></i>
                            <span slot="title">{{ item.title }}</span>
                        </template>
                        <template v-for="subItem in item.subs">
                            <el-submenu
                                v-if="subItem.subs"
                                :index="subItem.index"
                                :key="subItem.index"
                            >
                                <template slot="title"><i :class="subItem.icon"></i>{{ subItem.title }}</template>
<!--                                二级菜单-->
                                <el-menu-item
                                    v-for="(threeItem,i) in subItem.subs"
                                    :key="i"
                                    :index="threeItem.index"
                                ><i :class="threeItem.icon"></i>{{ threeItem.title }}</el-menu-item>
                            </el-submenu>
                            <!-- :key="subItem.index" 删掉的39行 -->
                            <el-menu-item
                                v-else
                                :index="subItem.index"
                                
                            ><i :class="subItem.icon"></i>{{ subItem.title }}</el-menu-item>
                        </template>
                    </el-submenu>
                </template>
                <template v-else>
                    <el-menu-item :index="item.index" :key="item.index">
                        <i :class="item.icon"></i>
                        <span slot="title">{{ item.title }}</span>
                    </el-menu-item>
                </template>
            </template>
        </el-menu>
    </div>
</template>

<script>
import bus from '../common/bus';
export default {
    data() {
        return {
            collapse: false,
            items:[],
            //管理员菜单userType=0
            itemList3: [
                {
                    "id":4,
                    "pid":1,
                    "icon":"el-icon-s-order",
                    "index":"3",
                    "title":"统一管理",
                    "subs":[
                        {
                            "id":9,
                            "pid":4,
                            "icon":"el-icon-plus",
                            "index":"user",
                            "title":"用户管理",
                            "subs":null
                        },
                        {
                            "id":10,
                            "pid":4,
                            "icon":"el-icon-plus",
                            "index":"test",
                            "title":"题库管理",
                            "subs":null
                        },
                        {
                            "id":11,
                            "pid":4,
                            "icon":"el-icon-plus",
                            "index":"question2",
                            "title":"选题管理",
                            "subs":null
                        },
                        {
                            "id":11,
                            "pid":4,
                            "icon":"el-icon-plus",
                            "index":"report",
                            "title":"成绩管理",
                            "subs":null
                        },
                        {
                                "id":12,
                                "pid":4,
                                "icon":"el-icon-plus",
                                "index":"parent",
                                "title":"家长管理",
                                "subs":null
                            },
                    ]
                },
            ],
            //教师菜单userType=1
            itemList: [
                    {
                        "id":4,
                        "pid":1,
                        "icon":"el-icon-s-order",
                        "index":"3",
                        "title":"统一管理",
                        "subs":[
                            {
                                "id":9,
                                "pid":4,
                                "icon":"el-icon-plus",
                                "index":"user",  //对应就是 /user路径,即在router中是对应user.vue
                                "title":"用户管理",
                                "subs":null
                            },
                            {
                                "id":10,
                                "pid":4,
                                "icon":"el-icon-plus",
                                "index":"test",
                                "title":"题库管理",
                                "subs":null
                            },
                            {
                                "id":11,
                                "pid":4,
                                "icon":"el-icon-plus",
                                "index":"question2",
                                "title":"选题管理",
                                "subs":null
                            },
                            {
                                "id":11,
                                "pid":4,
                                "icon":"el-icon-plus",
                                "index":"report",
                                "title":"成绩管理",
                                "subs":null
                            },
                            {
                                "id":12,
                                "pid":4,
                                "icon":"el-icon-plus",
                                "index":"parent",
                                "title":"家长管理",
                                "subs":null
                            },
                            {
                                "id":12,
                                "pid":4,
                                "icon":"el-icon-plus",
                                "index":"test2",
                                "title":"测试2",
                                "subs":null
                            },
                        ]
                    },
                  ],
            //学生菜单userType=2      
            itemList2:[
                {
                    "id":5,
                    "pid":1,
                    "icon":"el-icon-s-data",
                    "index":"6",
                    "title":"我的管理",
                    "subs":[
                        {
                            "id":10,
                            "pid":4,
                            "icon":"el-icon-plus",
                            "index":"test",
                            "title":"题库管理",
                            "subs":null
                        },
                        {
                            "id":11,
                            "pid":4,
                            "icon":"el-icon-plus",
                            "index":"report",
                            "title":"成绩管理",
                            "subs":null
                        },
                        {
                            "id":11,
                            "pid":4,
                            "icon":"el-icon-plus",
                            "index":"collect",
                            "title":"错题管理",
                            "subs":null
                        },
                        {
                                "id":12,
                                "pid":4,
                                "icon":"el-icon-plus",
                                "index":"parent",
                                "title":"家长管理",
                                "subs":null
                            },
                    ]
                }
            ],
        };
    },
    computed: {
        onRoutes() {
            return this.$route.path.replace('/', '');
        },
    },
    created() {
        // 通过 Event Bus 进行组件间通信,来折叠侧边栏
        bus.$on('collapse', msg => {
            this.collapse = msg;
            bus.$emit('collapse-content', msg);
        });
        //初始化menuList
        if ("1" === sessionStorage.getItem('userType')){
            this.items = this.itemList; //学生的菜单
        }else if ("2" === sessionStorage.getItem('userType')){
            this.items = this.itemList2;  //教师的菜单
        }else {
            this.items = this.itemList3;  //管理员的菜单
        }
    }
};
</script>

<style scoped>
.sidebar {
    display: block;
    position: absolute;
    left: 0;
    top: 70px;
    bottom: 0;
    overflow-y: scroll;
}
.sidebar::-webkit-scrollbar {
    width: 0;
}
.sidebar-el-menu:not(.el-menu--collapse) {
    width: 250px;
}
.sidebar > ul {
    height: 100%;
}
</style>

8singleupload.vue

html 复制代码
<template> 
  <div>
    <el-upload
            action="http://gulimall-psw.oss-cn-hangzhou.aliyuncs.com"
            :data="dataObj"
            list-type="picture"
            :multiple="false" :show-file-list="showFileList"
            :file-list="fileList"
            :before-upload="beforeUpload"
            :on-remove="handleRemove"
            :on-success="handleUploadSuccess"
            :on-preview="handlePreview">
      <el-button size="small" type="primary">点击上传</el-button>
<!--      <div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过10MB</div>-->
    </el-upload>
    <el-dialog :visible.sync="dialogVisible">
      <img width="50%" :src="fileList[0].url" alt="">
    </el-dialog>
  </div>
</template>
<script>


  import { getUUID, policy } from '../../api/policy';

  export default {
    name: 'singleUpload',
    props: {
      value: String
    },
    computed: {
      imageUrl() {
        return this.value;
      },
      imageName() {
        if (this.value != null && this.value !== '') {
          return this.value.substr(this.value.lastIndexOf("/") + 1);
        } else {
          return null;
        }
      },
      fileList() {
        return [{
          name: this.imageName,
          url: this.imageUrl
        }]
      },
      showFileList: {
        get: function () {
          return this.value !== null && this.value !== ''&& this.value!==undefined;
        },
        set: function (newValue) {
        }
      }
    },
    data() {
      return {
        dataObj: {
          policy: '',
          signature: '',
          key: '',
          ossaccessKeyId: '',
          dir: '',
          host: '',
          // callback:'',
        },
        dialogVisible: false
      };
    },
    methods: {
      emitInput(val) {
        this.$emit('input', val)
      },
      handleRemove(file, fileList) {
        this.emitInput('');
      },
      handlePreview(file) {
        this.dialogVisible = true;
      },
      beforeUpload(file) {
        let _self = this;
        return new Promise((resolve, reject) => {
          policy().then(response => {
            console.log(response)
            _self.dataObj.policy = response.policy;
            _self.dataObj.signature = response.signature;
            _self.dataObj.ossaccessKeyId = response.accessid;
            _self.dataObj.key = response.dir + '/'+getUUID()+'_${filename}';
            _self.dataObj.dir = response.dir;
            _self.dataObj.host = response.host;
            resolve(true)
          }).catch(err => {
            reject(false)
          })
        })
      },
      handleUploadSuccess(res, file) {
        console.log("上传成功...")
        this.showFileList = true;
        this.fileList.pop();
        this.fileList.push({name: file.name, url: this.dataObj.host + '/' + this.dataObj.key.replace("${filename}",file.name) });
        this.emitInput(this.fileList[0].url);
      }
    }
  }
</script>
<style>

</style>

9Tags.vue

html 复制代码
<template>
    <div class="tags" v-if="showTags">
        <ul>
            <li class="tags-li" v-for="(item,index) in tagsList" :class="{'active': isActive(item.path)}" :key="index" v-if="item.title !=='开始考试'">
                <router-link :to="item.path" class="tags-li-title">
                    {{item.title}}
                </router-link>
                <!-- <span class="tags-li-icon" @click="closeTags(index)"><i class="el-icon-close"></i></span> -->
                <!-- 只有当标题不是"系统首页"时才关闭按钮 -->
                <span v-if="item.title !== '系统首页'" class="tags-li-icon" @click="closeTags(index)"><i class="el-icon-close"></i></span>
            
            </li>
        </ul>
        <div class="tags-close-box">
            <el-dropdown @command="handleTags">
                <el-button size="mini" type="primary">
                    标签选项<i class="el-icon-arrow-down el-icon--right"></i>
                </el-button>
                <el-dropdown-menu size="small" slot="dropdown">
                    <el-dropdown-item command="other">关闭其他</el-dropdown-item>
                    <el-dropdown-item command="all">关闭所有</el-dropdown-item>
                </el-dropdown-menu>
            </el-dropdown>
        </div>
    </div>
</template>

<script>
    import bus from './bus';
    export default {
        data() {
            return {
                tagsList: []
            }
        },
        methods: {
            isActive(path) {
                return path === this.$route.fullPath;
            },
            // 关闭单个标签
            closeTags(index) {
                const delItem = this.tagsList.splice(index, 1)[0];
                const item = this.tagsList[index] ? this.tagsList[index] : this.tagsList[index - 1];
                if (item) {
                    delItem.path === this.$route.fullPath && this.$router.push(item.path);
                }else{
                    // if (sessionStorage.getItem('userType') === '1'){
                    //     this.$router.push('/');
                    // }else {
                    //     this.$router.push('/test');
                    // }
                    this.$router.push('/');  //如果关闭了最后一个标签页,则到根路径去
                }
            },
            // 关闭全部标签
            closeAll(){
                this.tagsList = [];
                this.$router.push('/');
                
            },
            // 关闭其他标签
            closeOther(){
                const curItem = this.tagsList.filter(item => {
                    return item.path === this.$route.fullPath;
                })
                this.tagsList = curItem;
            },
            // 设置标签
            setTags(route){
                const isExist = this.tagsList.some(item => {
                    return item.path === route.fullPath;
                })
                if(!isExist){
                    if(this.tagsList.length >= 8){
                        this.tagsList.shift();
                    }
                    this.tagsList.push({
                        title: route.meta.title,
                        path: route.fullPath,
                        name: route.matched[1].components.default.name
                    })
                }
                bus.$emit('tags', this.tagsList);
            },
            handleTags(command){
                command === 'other' ? this.closeOther() : this.closeAll();
            }
        },
        computed: {
            showTags() {
                return this.tagsList.length > 0;
            }
        },
        watch:{
            $route(newValue, oldValue){
                this.setTags(newValue);
            }
        },
        created(){
            this.setTags(this.$route);
            // 监听关闭当前页面的标签页
            bus.$on('close_current_tags', () => {
                for (let i = 0, len = this.tagsList.length; i < len; i++) {
                    const item = this.tagsList[i];
                    if(item.path === this.$route.fullPath){
                        if(i < len - 1){
                            this.$router.push(this.tagsList[i+1].path);
                        }else if(i > 0){
                            this.$router.push(this.tagsList[i-1].path);
                        }else{
                            this.$router.push('/');
                        }
                        this.tagsList.splice(i, 1);
                        break;
                    }
                }
            })
        }
    }

</script>


<style>
    .tags {
        position: relative;
        height: 30px;
        overflow: hidden;
        background: #ffffff;
        padding-right: 120px;
        box-shadow: 0 5px 10px #d1faff;
    }

    .tags ul {
        box-sizing: border-box;
        width: 100%;
        height: 100%;
    }

    .tags-li {
        float: left;
        margin: 3px 5px 2px 3px;
        border-radius: 3px;
        font-size: 12px;
        overflow: hidden;
        cursor: pointer;
        height: 23px;
        line-height: 23px;
        border: 1px solid #e9eaec;
        background: #ffffff;
        padding: 0 5px 0 12px;
        vertical-align: middle;
        color: #666;
        -webkit-transition: all .3s ease-in;
        -moz-transition: all .3s ease-in;
        transition: all .3s ease-in;
    }

    .tags-li:not(.active):hover {
        background: #e97e84;
    }

    .tags-li.active {
        color: #000000;
    }

    .tags-li-title {
        float: left;
        max-width: 80px;
        overflow: hidden;
        white-space: nowrap;
        text-overflow: ellipsis;
        margin-right: 5px;
        color: #000000;
        font-weight: bolder;
    }

    .tags-li.active .tags-li-title {
        color: #fdfdfd;
        font-weight: bolder;
    }

    .tags-close-box {
        position: absolute;
        right: 0;
        top: 0;
        box-sizing: border-box;
        padding-top: 1px;
        text-align: center;
        width: 110px;
        height: 30px;
        background: #ffffff;
        box-shadow: -3px 0 15px 3px rgba(255, 16, 16, 0.1);
        z-index: 10;
    }

</style>
相关推荐
web守墓人1 小时前
【前端】ikun-markdown: 纯js实现markdown到富文本html的转换库
前端·javascript·html
Savior`L1 小时前
CSS知识复习5
前端·css
许白掰1 小时前
Linux入门篇学习——Linux 工具之 make 工具和 makefile 文件
linux·运维·服务器·前端·学习·编辑器
中微子5 小时前
🔥 React Context 面试必考!从源码到实战的完整攻略 | 99%的人都不知道的性能陷阱
前端·react.js
秋田君6 小时前
深入理解JavaScript设计模式之命令模式
javascript·设计模式·命令模式
中微子6 小时前
React 状态管理 源码深度解析
前端·react.js
风吹落叶花飘荡7 小时前
2025 Next.js项目提前编译并在服务器
服务器·开发语言·javascript
加减法原则7 小时前
Vue3 组合式函数:让你的代码复用如丝般顺滑
前端·vue.js
yanlele8 小时前
我用爬虫抓取了 25 年 6 月掘金热门面试文章
前端·javascript·面试
lichenyang4538 小时前
React移动端开发项目优化
前端·react.js·前端框架