25 Коміти 9769d9060e ... ca0c730120

Автор SHA1 Опис Дата
  icenaked ca0c730120 Merge branch 'seec' 3 роки тому
  icenaked 9ad0a24778 feat: 可以看到收藏者电话,方便双向联系 3 роки тому
  icenaked 675bad18dd feat: 交流模块完成 3 роки тому
  icenaked ce08a9c918 feat: 点收藏不会触发跳转详情 3 роки тому
  icenaked 2a45b1a3e9 fix: 调试发布bug 3 роки тому
  icenaked 129bc4160f feat: 交流item布局优化 3 роки тому
  icenaked 0b0196800c fix: 发布交流类型调整 3 роки тому
  DingXiaoYu a8d4c89ddc Merge remote-tracking branch 'origin/seec' into seec 3 роки тому
  DingXiaoYu cf1a7f53d2 feat:完成发布交流 3 роки тому
  icenaked efd1f2680c feat: 交流模块其他页面 3 роки тому
  icenaked b792e68ca7 feat: 收藏交流 3 роки тому
  icenaked d8a273c598 feat: 列表部分(todo: 发布者、列表联系方式优化) 3 роки тому
  DingXiaoYu 281d4f4a35 fix:修复了交流的bug 3 роки тому
  icenaked 9195ada9f9 fix: api bug 3 роки тому
  icenaked 8e7001a208 feat: 对接列表 3 роки тому
  DingXiaoYu 3ebc630330 feat:补充了交流的接口 3 роки тому
  icenaked ba948c1251 fix: 文字细节 3 роки тому
  icenaked a4e75119c2 feat: 完善交流详情页面 3 роки тому
  icenaked 349d5ae869 feat: 交流详情大致页面,调整目录结构 3 роки тому
  icenaked 3656083ee8 feat: 收藏变色不触发刷新,查看收藏的用户 3 роки тому
  icenaked 57b041c7ef feat: 分页 3 роки тому
  icenaked c6683d3c56 feat: 交流发现页面组件通信与样式调整 3 роки тому
  icenaked 7ded8f6a8b feat: 交流管理页面,修改交流模块部分vue名称 3 роки тому
  mjh 5a9263a3cc feat: 教师交流平台的前端实现 3 роки тому
  mjh 9faf888bf0 feat: 教师交流平台的前端实现 3 роки тому

+ 1 - 1
public/index.html

@@ -17,7 +17,7 @@
         }
         body {
             margin: 0;
-            min-width: 1100px;
+            min-width: 1200px;
             background: #f8f9fb;
         }
     </style>

+ 1 - 0
src/App.vue

@@ -36,6 +36,7 @@ export default {
 </script>
 
 <style lang="scss" scoped>
+@import "main.css";
 /*Zhurui修改,消除底部对主体元素的覆盖*/
 .main-body {
   //margin-bottom: 250px;

+ 60 - 0
src/api/communication.js

@@ -0,0 +1,60 @@
+import {axios} from "@/requests/axios.config";
+
+const prefix = "/communication";
+
+function getAllCommunications(payload) {
+    const {page, size, sort} = payload;
+     return axios.get(`${prefix}`, {params: {page, size, sort}});
+
+}
+
+function getCommunicationStarers(communicationId) {
+    return axios.get(`${prefix}/${communicationId}/stars/users`);
+}
+
+function getCommunicationDetail(communicationId) {
+    return axios.get(`${prefix}/${communicationId}`);
+
+}
+
+function getUserPublishCommunication(payload){
+    const {userId, page, size, sort} = payload;
+    return axios.get(`${prefix}/users/${userId}`,{params:{page, size, sort}});
+}
+
+function starCommunication(userId,communicationId){
+    return axios.post(`${prefix}/${communicationId}/stars?userId=${userId}`);
+}
+
+function cancelStarCommunication(userId,communicationId){
+    return axios.delete(`${prefix}/${communicationId}/stars`,{params:{userId}});
+}
+
+function getStarredCommunication(payload){
+    const {userId, page, size, sort} = payload;
+    return axios.get(`${prefix}/stars/users/${userId}`,{params:{page, size, sort}});
+}
+
+function uploadFile(file){
+    let data = new FormData();
+    data.append("file", file);
+    return axios.post(`${prefix}/upload`, data);
+}
+
+function postCommunication(communication){
+    return axios.post(`${prefix}`,communication);
+}
+
+const communicationAPIs = {
+    getAllCommunications,
+    getCommunicationStarers,
+    getCommunicationDetail,
+    getUserPublishCommunication,
+    starCommunication,
+    cancelStarCommunication,
+    getStarredCommunication,
+    uploadFile,
+    postCommunication,
+};
+
+export default communicationAPIs;

+ 3 - 1
src/api/index.js

@@ -8,6 +8,7 @@ import messageAPIs from "@/api/message";
 import groupAPIS from  "@/api/group";
 import projectAPIs from "@/api/project";
 import courseWareAPIs from "@/api/courseWare";
+import communicationAPIs from "@/api/communication";
 
 const api = {
   ...userAPIs,
@@ -19,7 +20,8 @@ const api = {
   ...messageAPIs,
   ...groupAPIS,
   ...projectAPIs,
-  ...courseWareAPIs
+  ...courseWareAPIs,
+  ...communicationAPIs
 };
 
 export default api;

BIN
src/assets/example.png


+ 4 - 1
src/components/GlobalHeader/GlobalHeader.vue

@@ -21,6 +21,9 @@
       <el-menu-item v-if="isTeacher" index="7">
         管理
       </el-menu-item>
+        <el-menu-item v-if="isTeacher" index="10">
+            交流
+        </el-menu-item>
       <el-menu-item index="6">
         个人中心
       </el-menu-item>
@@ -85,7 +88,7 @@ export default {
         ["7", "Manage"],
         ["8", "Problem"],
         ["9", "CourseDetail"],
-
+        ["10", "Communication"]
       ]
     };
   },

+ 4 - 3
src/global/register-element.js

@@ -37,10 +37,10 @@ import {
   ElMenuItem,
   ElSubmenu, ElCollapse, ElCollapseItem,
   ElSwitch,
-  ElContainer,ElHeader,ElMain,
+  ElContainer, ElHeader, ElMain,
   ElBadge, ElOptionGroup, ElTooltip,
   ElAlert,
-  ElProgress
+  ElProgress, ElCheckboxGroup
 } from "element-plus";
 
 // NOTE 使用到的组件列表,请确保 import 和 component 的一致性
@@ -92,7 +92,8 @@ const components = [
   ElHeader,
   ElMain,
   ElAlert,
-  ElProgress
+  ElProgress,
+  ElCheckboxGroup
 ];
 
 export default function (app) {

+ 9 - 0
src/main.css

@@ -0,0 +1,9 @@
+:root{
+
+}
+
+.current{
+    color: var(--el-color-primary);
+    border-color: var(--el-color-primary-light-7);
+    background-color: var(--el-color-primary-light-9);
+}

+ 34 - 1
src/router/index.js

@@ -139,7 +139,40 @@ const routes = [
   {
     path: "/root",
     name: "Root",
-    component: ()=>import("../views/RootPage/index.vue")
+    component: ()=>import("../views/RootPage/RootPage.vue")
+  },
+  {
+    path: "/communication",
+    name: "Communication",
+    component: ()=>import("../views/CommunicationPage/CommunicationPage.vue"),
+    redirect: "/communication/explore",
+    children:[
+      {
+        path:"explore",
+        name: "CommunicationExplore",
+        component: ()=>import("../views/CommunicationPage/ExplorePage.vue")
+      },
+      {
+        path:"publish",
+        name: "CommunicationPublish",
+        component: ()=>import("../views/CommunicationPage/PublishPage.vue")
+      },
+      {
+        path:"manage",
+        name: "CommunicationManage",
+        component: ()=>import("../views/CommunicationPage/ManagePage.vue")
+      },
+      {
+        path:"star",
+        name: "CommunicationStar",
+        component: ()=>import("../views/CommunicationPage/StarPage.vue")
+      },
+      {
+        path:"communicationDetail/:communicationId",
+        name: "CommunicationDetail",
+        component: ()=>import("../views/CommunicationPage/CommunicationDetail.vue")
+      }
+    ],
   }
 ];
 

+ 294 - 0
src/views/CommunicationPage/CommunicationDetail.vue

@@ -0,0 +1,294 @@
+<template>
+  <div class="communication-detail-page">
+    <!--课程信息-->
+    <div class="communication-detail-container">
+      <!--课程基本信息-->
+      <el-card>
+        <div class="header">
+          <img :src="titleImg" alt="err" class="communication-img">
+          <div class="communication-info">
+            <el-row style="font-size: xx-large; font-weight: bold">
+                {{ communicationInfo.title }}
+            </el-row>
+            <div class="communication-info-main">
+              <el-row>发布作者: {{ communicationInfo.publisherName }}</el-row>
+              <el-row>所属组织: {{ communicationInfo.organization }}</el-row>
+              <el-row>持续时间:</el-row>
+              <el-row style="text-indent: 1em"> {{ publishTime }}~{{ deadline }}</el-row>
+            </div>
+            <div class="explore-item-star" >
+              <el-tooltip placement="top-start" effect="light">
+                <template #content>
+                  <p style="max-width:500px;">{{starersListString}}收藏了本交流</p>
+                </template>
+                <div>
+                  <el-icon v-if="communicationInfo.starred===false" style="font-size: 20px" @click="star()"><Star /></el-icon>
+                  <el-icon v-if="communicationInfo.starred===true" style="font-size: 20px;color: orange" @click="cancelStar()">
+                    <StarFilled />
+                  </el-icon>
+                </div>
+              </el-tooltip>
+            </div>
+            <el-row>
+              联系方式:
+            </el-row>
+            <el-row v-if="communicationInfo.phone !== ''">
+              电话:{{communicationInfo.phone}}
+            </el-row>
+            <el-row v-if="communicationInfo.qq !== ''">
+              qq:{{communicationInfo.qq}}
+            </el-row>
+            <el-row v-if="communicationInfo.wechat !== ''">
+              微信:{{communicationInfo.wechat}}
+            </el-row>
+            <el-row v-if="communicationInfo.email !== ''">
+              邮箱:{{communicationInfo.email}}
+            </el-row>
+          </div>
+        </div>
+        <el-divider/>
+        <div class="introduce">
+          <div class="title">详细描述:</div>
+          <div class="introduce-content">{{ communicationInfo.description }}</div>
+        </div>
+        <div class="pictureList">
+          <div class="title">图片:</div>
+          <div v-for="image in imgList" :key="image.id">
+            <img :src="image.fileUrl" alt="err" class="picture">
+          </div>
+
+        </div>
+        <div class="attachment">
+          <div class="title">附件:</div>
+          <div class="attachment-list"
+              v-for="attachment in attachments"
+              :key="attachment.id"
+              @click="downloadAttachment(attachment.fileUrl, attachment.fileName)"
+          >
+            {{attachment.fileName}}{{attachment.fileExtention}}({{attachment.fileSize}})
+          </div>
+        </div>
+      </el-card>
+    </div>
+  </div>
+</template>
+
+<script>
+import {ElMessage} from "element-plus";
+import communication from "@/api/communication";
+
+export default {
+  name: "CommunicationDetail",
+  data(){
+    return{
+      communicationInfo: {},
+      titleImg: "",
+      imgList: [],
+      attachments: [],
+      starersList: [],
+      starersListString: "",
+      publishTime: "",
+      deadline: ""
+    };
+  },
+  async mounted() {
+    this.fetchData();
+  },
+  methods: {
+    fetchData(){
+      this.$apis.getCommunicationDetail(this.$route.params.communicationId).then(res=>{
+        this.communicationInfo = res.data.data;
+        if (this.communicationInfo.photos.length !== 0){
+          this.titleImg = this.communicationInfo.photos[0].fileUrl;
+          this.imgList =  this.communicationInfo.photos;
+        }
+        else this.titleImg = this.$variables.noImgUrl;
+        this.attachments = this.communicationInfo.attachments;
+        this.publishTime = this.communicationInfo.publishTime.substring(0,10) + " " + this.communicationInfo.publishTime.substring(11,19);
+        if (this.deadline !== ""){
+          this.deadline = this.communicationInfo.deadline.substring(0,10) + " " + this.communicationInfo.deadline.substring(11,19);
+        }
+        else this.deadline = "无截止日期";
+      });
+      this.$apis.getCommunicationStarers(this.$route.params.communicationId).then(starRes=>{
+            this.starersList = starRes.data.data;
+            this.starersListString = "";
+            this.starersList.forEach(starer=>{
+              this.starersListString = this.starersListString + starer.username + ",";
+            });
+            if (this.starersListString === ""){
+              this.starersListString = "暂无用户";
+            }
+            else this.starersListString = this.starersListString.substr(0, this.starersListString.length-1);
+      });
+    },
+    star(){
+      this.communicationInfo.starred = !this.communicationInfo.starred;
+      this.$apis.starCommunication(this.$store.state.user.id, this.communicationInfo.id);
+      ElMessage.success("收藏成功");
+    },
+    cancelStar(){
+      this.communicationInfo.starred = !this.communicationInfo.starred;
+      this.$apis.cancelStarCommunication(this.$store.state.user.id, this.communicationInfo.id);
+      ElMessage.success("取消收藏成功");
+    },
+    downloadAttachment(url, fileName){
+      console.log("下载!");
+      const data = url;
+      let file = new Blob(["\ufeff" + data]);
+      if (window.navigator.msSaveOrOpenBlob)
+          // IE10+
+        window.navigator.msSaveOrOpenBlob(file, fileName);
+      else {
+        // Others
+        let a = document.createElement("a"),
+            url = URL.createObjectURL(file);
+        a.href = url;
+        a.download = fileName;
+        document.body.appendChild(a);
+        a.click();
+        setTimeout(function() {
+          document.body.removeChild(a);
+          window.URL.revokeObjectURL(url);
+        }, 0);
+      }
+    }
+  },
+};
+</script>
+
+<style scoped>
+.explore-item-star{
+  cursor: pointer;
+}
+.attachment{
+  margin-top: 20px;
+}
+.attachment-list{
+  margin-top: 20px;
+  text-indent: 20px;
+  text-decoration: underline;
+  color: #409eff;
+}
+.attachment-list:hover{
+  margin-top: 20px;
+  text-indent: 20px;
+  text-decoration: underline;
+  color: #337ecc;
+  cursor: pointer;
+}
+.communication-detail-page {
+  margin: 40px 0 80px 0;
+  min-height: calc(100vh - 100px);
+}
+
+.communication-detail-container {
+  box-sizing: border-box;
+  padding: 0 100px;
+  margin-top: 32px;
+}
+
+.header {
+  display: flex;
+  justify-content: flex-start;
+  margin: 40px 10vw 80px 10vw;
+}
+
+.communication-img {
+    width: 320px;
+    height: 230px;
+    margin-left: -120px;
+}
+
+.communication-img > img {
+  width: 100%;
+  height: 100%;
+  object-fit: cover;
+  object-position: center center;
+}
+
+.pictureList{
+  margin-top: 20px;
+  text-align: center;
+}
+
+.picture{
+  max-width: 600px;
+  text-align: center;
+}
+
+.communication-info {
+  font-size: 16px;
+  color: #333333;
+  margin-left: 32px;
+  margin-right: -80px;
+}
+
+.communication-info-main {
+  background-color: #f6f6f6;
+  color: #777777;
+  margin-top: 10px;
+  border-radius: 8px;
+  padding: 20px;
+  width: 350px;
+}
+
+.communication-info > div {
+  margin-bottom: 16px;
+}
+
+.communication-info > div:last-child {
+  margin-bottom: 0;
+}
+
+.communication-info .mooc-link .el-link {
+  font-size: 16px;
+  color: #409eff;
+}
+
+.communication-info .mooc-link .el-link:hover {
+  color: #79bbff;
+}
+
+.introduce-content {
+  white-space: pre-wrap;
+  margin-top: 16px;
+  font-size: 14px;
+  line-height: 26px;
+  color: #333333;
+  padding: 10px 0 10px 20px;
+  border-radius: 10px;
+  background: #f6f6f6;
+}
+
+.title {
+  color: #333333;
+  font-weight: 500;
+  font-size: 20px;
+  text-align: left;
+}
+
+.new-notice-content .new-notice-item {
+  height: 40px;
+  margin-bottom: 16px;
+  display: flex;
+  align-items: center;
+  justify-content: flex-start;
+  font-size: 16px;
+}
+
+.new-notice-item > span, .content-description-area > span {
+  min-width: 100px;
+}
+
+.new-notice-item .mini-name-icon {
+  margin-left: 16px;
+  border-radius: 50%;
+  width: 30px;
+  height: 30px;
+  line-height: 30px;
+  text-align: center;
+  color: white;
+
+}
+</style>

+ 102 - 0
src/views/CommunicationPage/CommunicationPage.vue

@@ -0,0 +1,102 @@
+<template>
+  <el-row id="test-page-body">
+    <el-col :span="3">
+      <el-card style="position: fixed;">
+        <el-menu  :default-active="activeIndex" router>
+          <el-menu-item index="/portal">
+            <el-icon>
+              <Back/>
+            </el-icon>
+            <span style="padding-left: 20px">返回首页</span>
+          </el-menu-item>
+          <el-menu-item index="/communication/explore">
+            <el-icon>
+              <Search/>
+            </el-icon>
+            <span style="padding-left: 20px">发现交流</span>
+          </el-menu-item>
+          <el-menu-item index="/communication/publish">
+            <el-icon>
+              <Edit/>
+            </el-icon>
+            <span style="padding-left: 20px">发布交流</span>
+          </el-menu-item>
+          <el-menu-item index="/communication/star">
+            <el-icon>
+              <Star/>
+            </el-icon>
+            <span style="padding-left: 20px">我的收藏</span>
+          </el-menu-item>
+          <el-menu-item index="/communication/manage">
+            <el-icon>
+              <User/>
+            </el-icon>
+            <span style="padding-left: 20px">我的发布</span>
+          </el-menu-item>
+        </el-menu>
+      </el-card>
+    </el-col>
+    <el-col :span="2"/>
+    <el-col :span="18">
+      <router-view/>
+    </el-col>
+  </el-row>
+</template>
+
+<script>
+export default {
+  name: "Communication",
+  data(){
+    return{
+      activeIndex: this.$route.path,
+    };
+  },
+  mounted() {
+    // window.addEventListener("scroll", this.handleMenuScroll);
+  },
+  methods: {
+    toPage(name) {
+      this.$router.push({name});
+    },
+    // handleMenuScroll(e){
+    //   const menu = document.getElementsByClassName("communication-page-menu")[0];
+    //   menu.style.top = window.pageYOffset + "px";
+    // }
+  }
+};
+</script>
+
+<style scoped>
+#communication-page {
+    display: flex;
+    /*height: calc(100vh - 60px - 220px);*/
+}
+
+.communication-page-aside {
+    flex-basis: 300px;
+    position: relative;
+}
+
+.communication-page-menu {
+    width: 100%;
+    position: absolute;
+}
+
+.menu-button {
+    display: block;
+    width: 200px;
+    margin: 15px auto auto;
+}
+
+#test-page-body {
+  margin: 40px 60px 80px 0;
+  min-height: calc(100vh - 340px);
+  position: relative;
+}
+
+.test-page-main {
+  display: grid;
+  grid-template-columns: 1fr 1fr 1fr 1fr;
+  grid-gap: 20px;
+}
+</style>

+ 157 - 0
src/views/CommunicationPage/ExplorePage.vue

@@ -0,0 +1,157 @@
+<template>
+    <div class="communication-explore">
+        <div class="explore-header">
+<!--            <div class="explore-selector">-->
+<!--                <el-checkbox :indeterminate="isIndeterminate" v-model="checkAll" @change="handleCheckAllChange">全选-->
+<!--                </el-checkbox>-->
+<!--                <el-checkbox-group v-model="checkedTypes" @change="handleCheckedTypesChange">-->
+<!--                    <el-checkbox v-for="type in types" :label="type" :key="type">{{ type }}</el-checkbox>-->
+<!--                </el-checkbox-group>-->
+<!--            </div>-->
+
+          <div class="explore-sorter">
+            <el-select class="sort-select" style="width: 150px;" placeholder="排序方式"
+                       v-model="sort"
+                       @change="sortChange"
+            >
+              <template #prefix>
+                  <span style="padding-left: 5px;">
+                    <i class="el-icon-sort"></i>
+                  </span>
+              </template>
+              <el-option label="发布时间升序" value="PUBLISH_TIME_ASC"></el-option>
+              <el-option label="发布时间降序" value="PUBLISH_TIME_DESC"></el-option>
+<!--              <el-option label="截止时间升序" value="deadlineAsc"></el-option>-->
+<!--              <el-option label="截止时间降序" value="deadlineDesc"></el-option>-->
+            </el-select>
+          </div>
+
+<!--          <div class="explore-search">-->
+<!--                <el-input placeholder="请输入搜索内容" v-model="content"></el-input>-->
+<!--                <el-button type="primary" style="margin-left: 10px">确认</el-button>-->
+<!--          </div>-->
+        </div>
+        <div class="explore-main">
+            <ExploreItem class="explore-item"
+                         v-for="communication in communicationList"
+                         :key="communication.id"
+                         :communicationVO="communication"
+                         @starChange="starChange(communication)"
+            />
+        </div>
+        <div class="pagination">
+          <el-pagination
+              layout="prev, pager, next"
+              :page-size="size"
+              :total="total"
+              @current-change="pageChange"
+          >
+          </el-pagination>
+        </div>
+    </div>
+</template>
+
+<script>
+import ExploreItem from "@/views/CommunicationPage/components/ExploreItem.vue";
+
+export default {
+  name: "Explore",
+  components: {ExploreItem},
+  data() {
+    return {
+      page: 0,
+      size: 5,
+      total: 0,
+      // explore-selector
+      checkAll: true,
+      checkedTypes: ["难题", "活动", "其他"],
+      types: ["难题", "活动", "其他"],
+      isIndeterminate: false,
+      // explore-search
+      content: "",
+      sort: "PUBLISH_TIME_ASC",
+      communicationList: [],
+    };
+  },
+  computed: {
+    userId() {
+      return Number(this.$store.state.user.id);
+    },
+  },
+  methods: {
+    fetchData(){
+      const payload = {
+        userId: this.userId,
+        page: this.page,
+        size: this.size,
+        sort: this.sort
+      };
+      this.$apis.getAllCommunications(payload).then(res=>{
+        this.communicationList = res.data.data.content;
+        this.total = res.data.data.totalElements;
+      });
+    },
+    handleCheckAllChange(val) {
+      this.checkedTypes = val ? this.types : [];
+      this.isIndeterminate = false;
+    },
+    handleCheckedTypesChange(value) {
+      let checkedCount = value.length;
+      this.checkAll = checkedCount === this.types.length;
+      this.isIndeterminate = checkedCount > 0 && checkedCount < this.types.length;
+    },
+    pageChange(currentPage){
+      this.page = currentPage - 1;
+      this.fetchData();
+    },
+    sortChange(){
+      this.fetchData();
+    },
+    starChange(communication){
+      communication.starred = !communication.starred;
+    }
+  },
+  async mounted() {
+    this.fetchData();
+  },
+};
+</script>
+
+<style scoped>
+.communication-explore {
+    flex: 1;
+    padding: 10px;
+}
+
+.explore-header{
+    display: flex;
+    justify-content: space-between;
+    margin-bottom: 20px;
+    position: relative;
+}
+
+.explore-sorter{
+    /*position: absolute;*/
+    left: 250px;
+    top: 5px;
+}
+
+.explore-search{
+    display: flex;
+    align-items: center;
+}
+
+.explore-main{
+    padding: 0 30px;
+}
+
+.explore-item{
+    margin-bottom: 10px;
+}
+
+.pagination{
+    display: flex;
+    align-items: center;
+    justify-content: center;
+}
+</style>

+ 126 - 0
src/views/CommunicationPage/ManagePage.vue

@@ -0,0 +1,126 @@
+<template>
+  <div class="communication-explore">
+    <div class="explore-main">
+      <ExploreItem class="explore-item"
+                   v-for="communication in communicationList"
+                   :key="communication.id"
+                   :communicationVO="communication"
+                   @starChange="starChange(communication)"
+      />
+    </div>
+    <div class="pagination">
+      <el-pagination
+          layout="prev, pager, next"
+          :page-size="size"
+          :total="total"
+          @current-change="pageChange"
+      >
+      </el-pagination>
+    </div>
+  </div>
+</template>
+
+<script>
+import ExploreItem from "@/views/CommunicationPage/components/ExploreItem.vue";
+
+export default {
+  name: "ManagePage",
+  components: {ExploreItem},
+  data() {
+    return {
+      page: 0,
+      size: 5,
+      total: 0,
+      // explore-selector
+      checkAll: true,
+      checkedTypes: ["难题", "活动", "其他"],
+      types: ["难题", "活动", "其他"],
+      isIndeterminate: false,
+      // explore-search
+      content: "",
+      sort: "PUBLISH_TIME_ASC",
+      communicationList: [],
+    };
+  },
+  computed: {
+    userId() {
+      return Number(this.$store.state.user.id);
+    },
+  },
+  methods: {
+    fetchData(){
+      const payload = {
+        userId: this.userId,
+        page: this.page,
+        size: this.size,
+        sort: this.sort
+      };
+      this.$apis.getUserPublishCommunication(payload).then(res=>{
+        this.communicationList = res.data.data.content;
+        this.total = res.data.data.totalElements;
+      });
+    },
+    handleCheckAllChange(val) {
+      this.checkedTypes = val ? this.types : [];
+      this.isIndeterminate = false;
+    },
+    handleCheckedTypesChange(value) {
+      let checkedCount = value.length;
+      this.checkAll = checkedCount === this.types.length;
+      this.isIndeterminate = checkedCount > 0 && checkedCount < this.types.length;
+    },
+    pageChange(currentPage){
+      this.page = currentPage - 1;
+      this.fetchData();
+    },
+    sortChange(){
+      this.fetchData();
+    },
+    starChange(communication){
+      communication.starred = !communication.starred;
+    }
+  },
+  async mounted() {
+    this.fetchData();
+  },
+};
+</script>
+
+<style scoped>
+.communication-explore {
+  flex: 1;
+  padding: 10px;
+}
+
+.explore-header{
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 20px;
+  position: relative;
+}
+
+.explore-sorter{
+  /*position: absolute;*/
+  left: 250px;
+  top: 5px;
+}
+
+.explore-search{
+  display: flex;
+  align-items: center;
+}
+
+.explore-main{
+  padding: 0 30px;
+}
+
+.explore-item{
+  margin-bottom: 10px;
+}
+
+.pagination{
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+</style>

+ 154 - 0
src/views/CommunicationPage/PublishPage.vue

@@ -0,0 +1,154 @@
+<template>
+    <div class="communication-publish">
+        <el-form label-position="right" label-width="80px" :model="form">
+            <el-form-item label="标题">
+                <el-input v-model="form.title"></el-input>
+            </el-form-item>
+            <el-form-item label="类别">
+                <el-select v-model="form.type" placeholder="请选择">
+                    <el-option
+                            v-for="item in typeOptions"
+                            :key="item.value"
+                            :label="item.label"
+                            :value="item.value">
+                    </el-option>
+                </el-select>
+            </el-form-item>
+            <el-form-item label="详细描述">
+                <span style="color: grey;font-size: 12px">推荐填写:技术背景、技术挑战、当前结果、技术诉求等内容</span>
+                <el-input type="textarea" :rows="2"
+                        placeholder="请输入内容"
+                        v-model="form.textarea">
+                </el-input>
+            </el-form-item>
+            <el-form-item label="单位院校">
+                <el-input v-model="form.organization"></el-input>
+            </el-form-item>
+            <el-form-item label="手机号">
+                <el-input v-model="form.phone"></el-input>
+            </el-form-item>
+            <el-form-item label="微信">
+                <el-input v-model="form.wechat"></el-input>
+            </el-form-item>
+            <el-form-item label="QQ">
+                <el-input v-model="form.qq"></el-input>
+            </el-form-item>
+            <el-form-item label="邮箱">
+                <el-input v-model="form.email"></el-input>
+            </el-form-item>
+            <el-form-item label="图片" >
+                <el-upload action="" :auto-upload="false" :limit="3" accept="image/*"
+                           :on-change="handlePictureChange"
+                           :before-remove="beforeRemoved"
+                           :on-exceed="handleExceed" list-type="picture"
+                >
+                    <el-button size="small" type="primary">点击上传</el-button>
+                </el-upload>
+            </el-form-item>
+            <el-form-item label="附件">
+                <el-upload action="" :auto-upload="false" :limit="3"
+                           :on-change="handleFileChange"
+                           :before-remove="beforeRemoved"
+                           :on-exceed="handleExceed">
+                    <el-button size="small" type="primary">点击上传</el-button>
+                </el-upload>
+            </el-form-item>
+            <el-form-item label="截止时间">
+                <el-date-picker
+                    v-model="form.deadline"
+                    type="datetime"
+                    size="mini"
+                />
+            </el-form-item>
+        </el-form>
+
+        <el-button size="big" type="primary" style="margin-left: 500px" @click="publish">发布</el-button>
+    </div>
+</template>
+
+<script>
+import {ElMessage} from "element-plus";
+export default {
+  name: "Publish",
+  data() {
+    return {
+      form: {
+        title: "",
+        organization: "",
+        phone: "",
+        wechat:"",
+        qq:"",
+        email:"",
+        deadline:null,
+        type: null,
+        textarea: "",
+        //pictureList:[],
+        //fileList:[],
+        photos:[],
+        attachments:[],
+      },
+      typeOptions: [
+        {
+        value: "PROBLEM",
+        label: "难题"
+      }, {
+        value: "NOTICE",
+        label: "通知"
+      }],
+    };
+  },
+    methods: {
+        handlePictureChange(file,fileList) {
+            //this.form.pictureList=fileList;
+            this.$apis.uploadFile(file.raw).then(res=>{
+              this.form.photos.push(res.data.data);
+            });
+        },
+        handleFileChange(file,fileList){
+            //this.form.fileList=fileList;
+          this.$apis.uploadFile(file.raw).then(res=>{
+            this.form.attachments.push(res.data.data);
+          });
+        },
+        beforeRemoved(){
+          ElMessage.error("不允许删除!") ;
+          return false;
+        },
+        handleExceed(file,fileList){
+            ElMessage.error("最多上传3个!");
+        },
+        publish(){
+                const communication={
+                    publisherId: Number(this.$store.state.user.id),
+                    title: this.form.title,
+                    type: this.form.type,
+                    description:this.form.textarea,
+                    organization: this.form.organization,
+                    phone: this.form.phone,
+                    wechat:this.form.wechat,
+                    qq:this.form.qq,
+                    email:this.form.email,
+                    publishTime:new Date(),
+                    deadline:this.form.deadline,
+                    photos:this.form.photos,
+                    attachments:this.form.attachments,
+                    publisherName:this.$store.state.user.username
+                };
+                 this.$apis.postCommunication(communication).then(res=>{
+                     console.log(res.data.data);
+                     ElMessage.success("发布成功");
+                     this.$router.go(0);
+                 });
+        }
+    }
+};
+</script>
+
+<style scoped>
+.communication-publish {
+    flex: 1;
+    margin: 10px 10px 0;
+    /*overflow: scroll;*/
+}
+
+</style>

+ 126 - 0
src/views/CommunicationPage/StarPage.vue

@@ -0,0 +1,126 @@
+<template>
+  <div class="communication-explore">
+    <div class="explore-main">
+      <ExploreItem class="explore-item"
+                   v-for="communication in communicationList"
+                   :key="communication.id"
+                   :communicationVO="communication"
+                   @starChange="starChange(communication)"
+      />
+    </div>
+    <div class="pagination">
+      <el-pagination
+          layout="prev, pager, next"
+          :page-size="size"
+          :total="total"
+          @current-change="pageChange"
+      >
+      </el-pagination>
+    </div>
+  </div>
+</template>
+
+<script>
+import ExploreItem from "@/views/CommunicationPage/components/ExploreItem.vue";
+
+export default {
+  name: "StarPage",
+  components: {ExploreItem},
+  data() {
+    return {
+      page: 0,
+      size: 5,
+      total: 0,
+      // explore-selector
+      checkAll: true,
+      checkedTypes: ["难题", "活动", "其他"],
+      types: ["难题", "活动", "其他"],
+      isIndeterminate: false,
+      // explore-search
+      content: "",
+      sort: "PUBLISH_TIME_ASC",
+      communicationList: [],
+    };
+  },
+  computed: {
+    userId() {
+      return Number(this.$store.state.user.id);
+    },
+  },
+  methods: {
+    fetchData(){
+      const payload = {
+        userId: this.userId,
+        page: this.page,
+        size: this.size,
+        sort: this.sort
+      };
+      this.$apis.getStarredCommunication(payload).then(res=>{
+        this.communicationList = res.data.data.content;
+        this.total = res.data.data.totalElements;
+      });
+    },
+    handleCheckAllChange(val) {
+      this.checkedTypes = val ? this.types : [];
+      this.isIndeterminate = false;
+    },
+    handleCheckedTypesChange(value) {
+      let checkedCount = value.length;
+      this.checkAll = checkedCount === this.types.length;
+      this.isIndeterminate = checkedCount > 0 && checkedCount < this.types.length;
+    },
+    pageChange(currentPage){
+      this.page = currentPage - 1;
+      this.fetchData();
+    },
+    sortChange(){
+      this.fetchData();
+    },
+    starChange(communication){
+      communication.starred = !communication.starred;
+    }
+  },
+  async mounted() {
+    this.fetchData();
+  },
+};
+</script>
+
+<style scoped>
+.communication-explore {
+  flex: 1;
+  padding: 10px;
+}
+
+.explore-header{
+  display: flex;
+  justify-content: space-between;
+  margin-bottom: 20px;
+  position: relative;
+}
+
+.explore-sorter{
+  /*position: absolute;*/
+  left: 250px;
+  top: 5px;
+}
+
+.explore-search{
+  display: flex;
+  align-items: center;
+}
+
+.explore-main{
+  padding: 0 30px;
+}
+
+.explore-item{
+  margin-bottom: 10px;
+}
+
+.pagination{
+  display: flex;
+  align-items: center;
+  justify-content: center;
+}
+</style>

+ 189 - 0
src/views/CommunicationPage/components/ExploreItem.vue

@@ -0,0 +1,189 @@
+<template>
+    <el-card class="explore-item" shadow="never">
+
+        <div class="explore-item-description"
+             @click="this.$router.push('/communication/communicationDetail/' + this.communicationVO.id)">
+
+          <div class="explore-item-star" >
+            <el-tooltip placement="top-start" effect="light">
+              <template #content>
+                <p style="max-width:500px;">{{starersListString}}收藏了本交流</p>
+              </template>
+              <div>
+                <el-icon v-if="starred===false" style="font-size: 20px" @click.stop="star()"><Star /></el-icon>
+                <el-icon v-if="starred===true" style="font-size: 20px;color: orange" @click.stop="cancelStar()">
+                  <StarFilled />
+                </el-icon>
+              </div>
+            </el-tooltip>
+          </div>
+
+            <h1 class="description-title">{{ title}}</h1>
+            <div style="font-size: 14px;margin-top: 5px;margin-left: 10px;">
+              <span style="color: var(--el-color-danger)" v-if="type==='PROBLEM'">难题</span>
+              <span style="color: var(--el-color-success)" v-if="type==='NOTICE'">通知</span>
+              <span style="color: var(--el-color-primary)" v-if="type!=='PROBLEM' && type!=='NOTICE'">其他</span>
+                <span style="margin-left: 14px">{{ publisherName }}</span>
+                <span style="margin-left: 14px">{{ organization }}</span>
+                <span style="margin-left: 14px">{{ contact }}</span>
+                <span style="margin-left: 14px">{{ publishTime }}~{{deadline}}</span>
+            </div>
+            <p class="description-detail">{{ description }}</p>
+        </div>
+
+        <img class="explore-item-img" :src="photo">
+
+    </el-card>
+
+</template>
+
+<script>
+import {ElMessage} from "element-plus";
+export default {
+  name: "ExploreItem",
+  props: {
+    communicationVO: Object,
+  },
+  data(){
+    return{
+      starersList: [],
+      starersListString: "",
+    };
+  },
+  computed: {
+    title() {
+      return this.communicationVO.title || "无标题";
+    },
+    publisherName() {
+      return this.communicationVO.publisherName || "匿名";
+    },
+    organization() {
+      return this.communicationVO.organization || "无单位院系";
+    },
+    contact() {
+      if (this.communicationVO.email != ""){
+        return this.communicationVO.email;
+      }
+      else if (this.communicationVO.qq != ""){
+        return "qq: " + this.communicationVO.qq;
+      }
+      else if (this.communicationVO.wechat != ""){
+        return "微信: " + this.communicationVO.wechat;
+      }
+      else if (this.communicationVO.phone != ""){
+        return "电话: " + this.communicationVO.phone;
+      }
+      else return "暂无联系方式";
+    },
+    type() {
+      return this.communicationVO.type;
+    },
+    description() {
+      return this.communicationVO.description || "无简介";
+    },
+    photo() {
+      return this.communicationVO.photos.length === 0 ? this.$variables.noImgUrl : this.communicationVO.photos[0].fileUrl;
+    },
+    publishTime(){
+      return this.communicationVO.publishTime.substring(0,10);
+    },
+    deadline(){
+      if (this.communicationVO.deadline != null){
+        return this.communicationVO.deadline.substring(0,10);
+      }
+      else return "无截止日期";
+    },
+    starred: {
+      get(){
+        return this.communicationVO.starred;
+      },
+      // set(val){
+      //   this.communicationVO.starred = val;
+      // }
+    },
+  },
+  methods: {
+    star(){
+      this.$emit("starChange",this.communicationVO);
+      this.$apis.starCommunication(this.$store.state.user.id, this.communicationVO.id);
+      ElMessage.success("收藏成功");
+    },
+    cancelStar(){
+      this.$emit("starChange",this.communicationVO);
+      this.$apis.cancelStarCommunication(this.$store.state.user.id, this.communicationVO.id);
+      ElMessage.success("取消收藏成功");
+    },
+    fetchData(){
+        const communicationId =  this.communicationVO.id;
+        this.$apis.getCommunicationStarers(communicationId).then(res=>{
+          this.starersList = res.data.data;
+          this.starersListString = "";
+          this.starersList.forEach(starer=>{
+            this.starersListString = this.starersListString + starer.username + "(" + starer.phone + ")" + ",";
+          });
+          if (this.starersListString === ""){
+            this.starersListString = "暂无用户";
+          }
+          else this.starersListString = this.starersListString.substr(0, this.starersListString.length-1);
+      });
+    },
+  },
+  mounted() {
+    this.fetchData();
+  }
+};
+</script>
+
+<style scoped>
+el-card {
+    width: 100%;
+}
+.explore-item-star{
+  cursor: pointer;
+}
+.explore-item {
+    --explore-item-height: 150px;
+    height: var(--explore-item-height);
+    border-radius: 10px;
+    position: relative;
+    cursor: pointer;
+    padding: 5px 5px 15px 0;
+}
+
+.explore-item-description {
+  margin-left: 10px;
+  /*position: absolute;*/
+  /*left: 20px;*/
+}
+
+.explore-item-img {
+    height: calc(var(--explore-item-height) - 10px);
+    /*aspect-ratio: 2/3;*/
+}
+
+:deep(.el-card__body) {
+    padding: 5px;
+    display: flex;
+    justify-content: space-between;
+}
+
+.description-detail {
+    margin-top: 10px;
+    -webkit-line-clamp: 2;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+    text-overflow: ellipsis;
+    color: var(--el-color-info);
+    margin-left: 10px;
+}
+
+.description-title {
+    margin: 0 0 0 10px;
+    -webkit-line-clamp: 1;
+    display: -webkit-box;
+    -webkit-box-orient: vertical;
+    overflow: hidden;
+    text-overflow: ellipsis;
+}
+</style>

+ 32 - 0
src/views/RootPage/RootPage.vue

@@ -0,0 +1,32 @@
+<template>
+  <div id="root-page">
+    <div id="root-menu">
+    </div>
+    <div id="root-main">
+      root-main
+    </div>
+  </div>
+</template>
+
+<script>
+export default {
+  name: "RootPage"
+};
+</script>
+
+<style scoped>
+#root-page {
+  display: flex;
+  height: 100%;
+}
+
+#root-menu {
+  height: calc(100vh - 280px);
+  flex-basis: 10vw;
+  background: aliceblue;
+}
+
+#root-main {
+  flex: 1;
+}
+</style>

+ 0 - 13
src/views/RootPage/index.vue

@@ -1,13 +0,0 @@
-<template>
-  <div>root</div>
-</template>
-
-<script>
-export default {
-  name: "RootPage"
-};
-</script>
-
-<style scoped>
-
-</style>