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>
相关推荐
陈随易2 小时前
Postman 被曝自动往 Claude Code 和 Codex 装插件
前端·后端·程序员
Dream-Y.ocean4 小时前
鸿蒙PC平台宅匠 3D 室内设计适配实战:基于 Electron 壳方案的 Three.js 双渲染引擎与参数化户型编辑器开发
javascript·3d·harmonyos
Csvn5 小时前
性能监控
前端
Highcharts.js5 小时前
Highcharts Gantt使用符号的自定义数据标签开发甘特图示例
javascript·甘特图·highcharts·图表开发·实战代码·响应式图表
Csvn5 小时前
构建工具
前端
FRing036 小时前
vue2干货总结3(computed计算属性、watch监听器)
开发语言·前端·vue.js·vue2
Java小卷6 小时前
JuanForm vs FormCreate!Vue3 表单引擎联动配置 PK!
vue.js·低代码
kyriewen8 小时前
Skills 榜前 10 我全装了:只有 3 个能直接用
前端·javascript·程序员
IT_陈寒8 小时前
当Java的hashCode遇上Lombok,我的缓存被全清了
前端·人工智能·后端
工控丫丫9 小时前
帆软看板中经常使用的公式函数及经常搭的组合
前端·javascript·数据库