山东大学项目实训Web实验室(WebLab)(七)学生端Layout

山东大学项目实训Web实验室(WebLab)(七)学生端Layout

前言

项目仓库
本项目是为开发一套容器化的开发、运行、测试环境,用以支持Web开发、程序设计等课程的实验教学。
代码内容均为我和肖同学共同完成。

任务目标

设计并且编写主界面
主界面内容:

  • 学生界面
    • 项目中心
      • 课程任务
      • 课程通知
      • 我的项目
    • 上传中心
    • 编辑中心
    • 组织中心
  • 老师界面
    • 组织中心
    • 发布中心
    • 学生课程作业

我的任务

  • 页面外观与布局设计
  • 页面交互设计
  • 页面编写

展示内容

  • 学生端 Layout
  • 学生端App.vue
  • 学生端router

代码

App.vue

<!--按照路由进行画面跳转-->
<template>
  <div>
    <router-view/>
  </div>
</template>

router/index.js

import { createRouter, createWebHistory } from 'vue-router';
import codingViewVue from '../views/coding-view.vue';
import registerViewVue from '../views/register-view.vue';
import loginViewVue from '../views/login-view.vue';
import forgetpasswViewVue from '../views/forgetpassw-view.vue';
import updatepasswViewVue from '../views/updatepassw-view.vue';
import personalViewVue from '../views/personal-view.vue';


const router = createRouter({
  history: createWebHistory(import.meta.env.BASE_URL),
  routes: [
    {
      path: "/",
      name: "home",
      component: loginViewVue,
      meta:{
        isLogin:false,
      }
    },
    {
      path: "/register",
      name: "register-view",
      component: registerViewVue,
      meta:{
        isLogin:false,
      }
    },
    {
      path: "/forget_password",
      name: "forgetpassw-view",
      component: forgetpasswViewVue,
      meta:{
        isLogin:false,
      }
    },
    {
      path: "/update_password",
      name: "update_password",
      component: updatepasswViewVue,
      meta:{
        isLogin:true,
      }
    },
    {
      path: "/coding",
      name: "coding",
      component:codingViewVue,
      meta:{
        isLogin:true,
      }
    },
    {
      path: "/personal",
      name: "personal",
      component:personalViewVue,
      meta:{
        isLogin:true,
      }
    },
  ],
});

export default router;

layout/topmenu.vue

<template>
  <el-menu :default-active="$props.activeIndex" class="el-menu-demo" mode="horizontal" @select="handleSelect">
    <el-menu-item index="1" disabled>WebLab</el-menu-item>
    <el-menu-item index="2" >项目中心</el-menu-item>
    <el-menu-item index="3">上传中心</el-menu-item>
    <el-menu-item index="4">编辑中心</el-menu-item>
<!--    <el-sub-menu index="4">-->
<!--      <template #title>待补充</template>-->
<!--      <el-menu-item index="4-1">item one</el-menu-item>-->
<!--      <el-menu-item index="4-2">item two</el-menu-item>-->
<!--      <el-menu-item index="4-3">item three</el-menu-item>-->
<!--      <el-sub-menu index="4-4">-->
<!--        <template #title>item four</template>-->
<!--        <el-menu-item index="4-4-1">item one</el-menu-item>-->
<!--        <el-menu-item index="4-4-2">item two</el-menu-item>-->
<!--        <el-menu-item index="4-4-3">item three</el-menu-item>-->
<!--      </el-sub-menu>-->
<!--    </el-sub-menu>-->
    <el-menu-item index="5">组织中心</el-menu-item>
    <el-sub-menu index="6" style="position: absolute;right:15px;">
      <template #title>
        <div class="block">
          <el-avatar :size="45" :icon="UserFilled" fit :src="avatarUrl" />
        </div>
      </template>
      <el-menu-item index="5-1" @click="personal()">个人资料更新</el-menu-item>
      <el-menu-item index="5-2" @click="updatePassword()">更新密码</el-menu-item>
      <!-- <el-menu-item index="5-2">item two</el-menu-item> -->
      <el-menu-item index="5-3" @click="logOut">登出</el-menu-item>
    </el-sub-menu>
  </el-menu>
  <div class="h-6"></div>
</template>

<script lang="ts">

import { UserFilled } from '@element-plus/icons-vue'
import { ref, defineComponent } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { useRouter } from "vue-router"
import { useLoginStore } from '@/stores/store';
import { request } from '@/network/request';

export default defineComponent({
  props: {
    activeIndex: String
  },
  emits:['onIndexChange'],
  setup(props,context) {

    const store = useLoginStore();
    const router = useRouter();
    // const activeIndex = ref(props.activeIndex);
    const activeIndex2 = ref('2');
    const avatarUrl=ref('');

  request('/weblab/user/getUserWithInfo', undefined)
  .then(res => {
    if (res.data.msg == 'success') {
      const data = res.data.pkg;
      if(data.headImg!=null)
        avatarUrl.value = data.headImg;
    }
  })
    const handleSelect = (key: string, keyPath: string[]) => {
      // activeIndex.value = key;
      context.emit('onIndexChange',key);
    }
    function logOut() {
      ElMessageBox.confirm(
        '是否确认退出?',
        '退出提示',
        {
          confirmButtonText: '确认',
          cancelButtonText: '取消',
        }
      )
        .then(() => {
          // store.userLogout();
          // router.push({ path: '/' });


          let param = new FormData();
          request('/weblab/user/logout', param, store.getToken)
            .then(res => {
              store.userLogout();
              router.push({ path: '/' });
              // if (res.status == 200 && res.data.msg == 'success') {
                
              // }
            })
            .catch(error => {
              console.log(error);
            })

        })
        .catch(() => {
          //点击取消
          // activeIndex.value = '2';
          context.emit('onIndexChange','2');
        })
    }
    function updatePassword() {
      router.push({ path: '/update_password' });
    }

    function personal() {
      router.push({ path: '/personal' });
    }

    return {
      // activeIndex,
      activeIndex2,
      handleSelect,
      logOut,
      updatePassword,
      UserFilled,
      personal,
      avatarUrl
    }
  },
});

</script>
<style>
</style>

layout/sidecolumn.vue

<template>
  <div class="custom-tree-container">
    <el-menu default-active="2" class="el-menu-vertical-demo">
      <el-input v-model="query" placeholder="输入关键词" style="width: 220px" />

      <el-tree ref="treeRef" :data="dataSource" :props="props" :filter-node-method="filterMethod" :height="720"
        node-key="id" default-expand-all @node-contextmenu="RightClick" @node-click="LeftClick" highlight-current>
        <template #default="{ node, data }">
          <el-icon>
            <folder v-if="data.type == FileType.folder || data.type == FileType.root"></folder>
            <document v-else></document>
          </el-icon>
          <span>{{ node.label }}</span>
        </template>
      </el-tree>

      <el-card v-show="visible" id="menu" shadow="always"
        :style="{ position: 'fixed', left: mouseX + 'px', top: mouseY + 'px', zIndex: '999', cursor: 'pointer' }">
        <div>
          <el-button type="text" @click="AddFoloder()"
            v-show="targetData?.type === FileType.folder || targetData?.type === FileType.root">添加文件夹
          </el-button>
        </div>
        <div>
          <el-button type="text" @click="AddFile()"
            v-show="targetData?.type === FileType.folder || targetData?.type === FileType.root">添加文件
          </el-button>
        </div>
        <div>
          <el-button type="text" @click="Delete()" v-show="targetData?.type != FileType.root">删除</el-button>
        </div>
        <div>
          <el-button type="text" @click="Rename()">重命名</el-button>
        </div>
      </el-card>
    </el-menu>
  </div>
</template>

<script lang="ts" setup>
import { ref, watch } from 'vue'
import type { ElTree } from 'element-plus'
import type Node from 'element-plus/es/components/tree/src/model/node'
import { ElMessage, ElMessageBox } from 'element-plus'
import { fileTypes, FileType, ziper } from '../fileziper';
import { Folder, Document } from '@element-plus/icons-vue';


interface Tree {
  id: string
  name: string
  type: FileType
  children: Tree[]
  value?: string
}

const visible = ref(false)

const query = ref('')
const treeRef = ref<InstanceType<typeof ElTree>>()
const targetData = ref<Tree>()
const targetNode = ref<Node>()
// const selectedData = ref<Tree>()
const totalId = ref(0);
const mouseX = ref(0)
const mouseY = ref(0)
const props = {
  value: 'id',
  label: 'name',
  children: 'children',
}
const dataSource = ref<Tree[]>
  ([
    {
      id: `${totalId.value++}`, name: 'Web', type: FileType.root, children:
        [
          {
            id: `${totalId.value++}`, name: 'src', type: FileType.folder, children:
              [
                {
                  id: `${totalId.value++}`, name: 'main.js', type: FileType.js, children: [], value: ''
                },
                {
                  id: `${totalId.value++}`, name: 'main.html', type: FileType.html, children: [], value: ''
                },
                {
                  id: `${totalId.value++}`, name: 'main.css', type: FileType.css, children: [], value: ''
                },
                {
                  id: `${totalId.value++}`, name: 'README.md', type: FileType.md, children: [], value: ''
                }
              ]
          },
          {
            id: `${totalId.value++}`, name: 'public', type: FileType.folder, children:
              [
                {
                  id: `${totalId.value++}`, name: 'README.md', type: FileType.md, children: [], value: ''
                }
              ]
          }
        ]
    }

  ])


watch(query, (val) => {
  treeRef.value!.filter(val)
})
const filterMethod = (query: string, data: Tree) => {
  if (!query) return true;
  return data.name!.includes(query)
}
const RightClick = function (e: PointerEvent, data: Tree, node: Node) {
  visible.value = true;
  targetData.value = data;
  targetNode.value = node;
  mouseX.value = e.clientX;
  mouseY.value = e.clientY;
  document.addEventListener('click', cancelRightClick)
}

const LeftClick = function (data: Tree) {
  visible.value = false;

  if (data.type != FileType.folder && data.type != FileType.root) {
    // selectedData.value = data;
    emit("setFileContext", data.name, data.id, data.value, data.type);
  } else {
    // selectedData.value = undefined;
  }
}

const cancelRightClick = function () {
  visible.value = false;
  document.removeEventListener('click', cancelRightClick);
}

const AddFile = () => {
  ElMessageBox.prompt('输入文件名称(name.type)', '添加新文件', {
    confirmButtonText: 'OK',
    cancelButtonText: 'Cancel',
    inputPattern:
      /[\w!#$%&'*+/=?^_`{|}~-]+(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/,
    inputErrorMessage: '请正确输入文件名称(name.type)',
  })
    .then(({ value }) => {
      const val = value.split(".");
      let type = val[val.length - 1];
      let fileType = fileTypes(type);
      if (fileType != undefined) {
        if (targetData.value != null) {
          for (let i = 0; i < targetData.value.children.length; i++) {
            if (fileType == targetData.value.children[i].type && value == targetData.value.children[i].name)
              throw false;
          }
        }

        targetData.value!.children.push({
          id: `${totalId.value++}`,
          name: value,
          type: fileType,
          children: [],
          value: ''
        })
        dataSource.value = [...dataSource.value]
      } else {
        ElMessage({
          type: 'error',
          message: `暂不支持该类型文件`,
        })
      }
      // ElMessage({
      //   type: 'success',
      //   message: `Your file is:${value}`,
      // })
    })
    .catch(() => {
      ElMessage({
        type: 'error',
        message: "文件已存在",
      })
    })
}
const AddFoloder = () => {
  ElMessageBox.prompt('输入文件夹名称', '添加文件夹', {
    confirmButtonText: 'OK',
    cancelButtonText: 'Cancel',
    inputPattern:
      /[\w!#$%&'*+/=?^_`{|}~-]?/,
    inputErrorMessage: '请输入文件夹名称',
  })
    .then(({ value }) => {
      if (targetData.value != null) {
        for (let i = 0; i < targetData.value.children.length; i++) {
          if (targetData.value.children[i].type == FileType.folder && value == targetData.value.children[i].name)
            throw false;
        }
      }
      targetData.value!.children.push({
        id: `${totalId.value++}`,
        name: value,
        type: FileType.folder,
        children: []
      })
      dataSource.value = [...dataSource.value]
      // ElMessage({
      //   type: 'success',
      //   message: `Your folder is:${value}`,
      // })
    })
    .catch(() => {
      ElMessage({
        type: 'error',
        message: "文件夹已经存在",
      })
    })
}
const Delete = function () {
  ElMessageBox.confirm('将删除该文件,继续?', 'Warning',
    {
      confirmButtonText: 'OK',
      cancelButtonText: 'Cancel',
      type: 'warning',
    })
    .then(() => {
      if (targetData.value?.type == FileType.folder) {
        deleteFile(targetData.value.children);
      } else {
        emit('deteleFile', targetData.value?.id);
      }
      const parent = targetNode.value?.parent
      const children: Tree[] = parent?.data.children || parent?.data
      const index = children.findIndex((d) => d.id === targetData.value?.id)
      children.splice(index, 1)
      dataSource.value = [...dataSource.value]
      // selectedData.value = undefined;
    })
    .catch(() => {
    })
}
const deleteFile = (data: Tree[]) => {
  for (var item in data) {
    if (data[item].type == FileType.folder) {
      deleteFile(data[item].children);
    } else {
      emit('deteleFile', data[item].id);
    }
  }
}
const Rename = function () {
  const isFolder = (targetData.value?.type === FileType.folder) || (targetData.value?.type === FileType.root);
  ElMessageBox.prompt('输入新名称', '重命名', {
    confirmButtonText: 'OK',
    cancelButtonText: 'Cancel',
    inputPattern: isFolder
      ? /[\w!#$%&'*+/=?^_`{|}~-]?/ : /[\w!#$%&'*+/=?^_`{|}~-]+(?:[\w](?:[\w-]*[\w])?\.)+[\w](?:[\w-]*[\w])?/,
    inputErrorMessage: '请正确输入新名称',
  })
    .then(({ value }) => {
      let fileType;
      if (targetNode.value?.data.type != FileType.root) {
        const pa = targetNode.value?.parent.data.children;
        if (!isFolder) {
          const val = value.split(".");
          let type = val[val.length - 1];
          fileType = fileTypes(type);

          for (let i = 0; i < pa.length; i++) {
            if (value == pa[i].name && fileType == pa[i].type)
              throw '文件';
          }
        } else {
          for (let i = 0; i < pa.length; i++) {
            if (pa[i].type == FileType.folder && value == pa[i].name)
              throw '文件夹';
          }
          fileType = FileType.folder;
        }
      }else{
        fileType=FileType.root;
      }
      if (fileType != undefined) {
        emit('rename', targetData.value?.id, value);
        targetData.value!.name = value;
        if (targetData.value?.id == '0') {
          ziper.setProjectName(value);
        }
        dataSource.value = [...dataSource.value]
      } else {
        ElMessage({
          type: 'error',
          message: `暂不支持该类型文件`,
        })
      }
      // ElMessage({
      //   type: 'success',
      //   message: `Your folder is:${value}`,
      // })
    })
    .catch((e) => {
      ElMessage({
        type: 'error',
        message: `${e}已存在`,
      })
    })
}
const emit = defineEmits(['setFileContext', 'deteleFile', 'rename']);
const setCode = (id: string, value: string) => {
  // if (selectedData.value != null) {
  //   selectedData.value.value = value;
  // }
  const node = treeRef.value?.getNode(id);
  if (node) {
    node.data.value = value;
  }

}
const getCode = (id: string) => {
  const node = treeRef.value?.getNode(id);
  return { "code": node?.data.value, "type": node?.data.type };
}
const getData = () => {
  return dataSource.value[0].children;
}
const getProjectName = () => {
  return dataSource.value[0].name;
}
defineExpose({ setCode, getData, getProjectName, getCode })
</script>

<style>
.dialog-footer button:first-child {
  margin-right: 10px;
}
</style>
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值