diff --git a/src/api/index.js b/src/api/index.js
index fc5421a..23e12d8 100644
--- a/src/api/index.js
+++ b/src/api/index.js
@@ -119,6 +119,22 @@ export default {
     stageGradeManagementList: `competition/competition/performance/stageGradeManagementList`,
     stageTeamScoreDetails: `competition/competition/rank/stageTeamScoreDetails`,
     getRedisCacheCompetition: `competition/competition/management/getRedisCache`,
+    
+    // 创业活动
+    activityList: `occupationlab/occupationlab/activity/activityList`,
+    schoolActivities: `occupationlab/occupationlab/activity/schoolActivities`,
+    batchDeletionActivity: `occupationlab/occupationlab/activity/batchDeletion`,
+    disabledEventsActivity: `occupationlab/occupationlab/activity/disabledEvents`,
+    findByIdActivity: `occupationlab/occupationlab/activity/findById`,
+    getRedisCacheActivity: `occupationlab/occupationlab/activity/getRedisCache`,
+    saveActivity: `occupationlab/occupationlab/activity/save`,
+    updateActivity: `occupationlab/occupationlab/activity/update`,
+    myActivities: `occupationlab/occupationlab/activity/myActivities`,
+    delActivityApplicant: `occupationlab/occupationlab/activity/applicant/batchDeletion`,
+    findByIdActivityApplicant: `occupationlab/occupationlab/activity/applicant/findById`,
+    saveActivityApplicant: `occupationlab/occupationlab/activity/applicant/save`,
+    updateActivityApplicant: `occupationlab/occupationlab/activity/applicant/update`,
+    ApplicantsList: `occupationlab/occupationlab/activity/applicant/ApplicantsList`,
 
     // 阿里云文件/视频管理
     fileDeletion: `${uploadURL}oss/manage/fileDeletion`, // 删除OSS文件
diff --git a/src/layouts/navbar/index.vue b/src/layouts/navbar/index.vue
index 655119d..4425751 100644
--- a/src/layouts/navbar/index.vue
+++ b/src/layouts/navbar/index.vue
@@ -51,6 +51,10 @@ export default {
                     index: "/match/list",
                     title: "线上赛事"
                 },
+                {
+                    index: "/activity/list",
+                    title: "创业活动"
+                },
                 {
                     index: "/screen",
                     title: "数据看板"
diff --git a/src/pages/activity/details/index.vue b/src/pages/activity/details/index.vue
new file mode 100644
index 0000000..3fb7616
--- /dev/null
+++ b/src/pages/activity/details/index.vue
@@ -0,0 +1,1212 @@
+<template>
+    <div class="wrap index">
+        <div class="banner" :style="{backgroundImage: 'url(' + (form.carouselUrl || 'https://huoran.oss-cn-shenzhen.aliyuncs.com/20220613/png/1536269450851409920.png') + ')'}"></div>
+        <div class="center-con">
+          <div class="center-wrap">
+              <breadcrumb ref="breadcrumb" :data="'全部赛事/' + form.name"></breadcrumb>
+              <div class="content">
+                  <div :class="['tool flex-between', {logView: !logView}]">
+                      <el-tabs v-model="curType" @tab-click="typeChange">
+                          <el-tab-pane v-for="(item, index) in typeList" :key="index" :label="item.name" :name="item.id"></el-tab-pane>
+                      </el-tabs>
+                      <div class="action">
+                          <!-- <p class="end-text" v-if="end">
+                              距离{{ endList[status] }}还有
+                              <em>{{ end }}</em>
+                          </p> -->
+                          <a v-if="status != 4 || (status == 4 && curStage)" class="status" :class="{wait: status == 0,signing: status == 2,signed: status == 1,playing: status == 4,finish: status == 3 || status == 5}" :title="statusList[status]" @click.stop="signup">{{ statusList[status] }}</a>
+                      </div>
+                  </div>
+                  <div class="info">
+                    <h6 class="title">{{ form.projectName }}</h6>
+                    <div class="meta">最近编辑时间:{{ form.updateTime }}</div>
+                  </div>
+                  
+                  <div v-show="curType < 4">
+                    <div class="l-title" id="part1"><img src="@/assets/img/label.png" alt=""> 项目信息</div>
+                    <div v-if="form.projectDescribe" class="texts ql-editor" v-html="form.projectDescribe"></div>
+                    <template v-if="form.activityFileList">
+                      <h6 class="p-title">附件下载</h6>
+                      <ul class="files">
+                        <li v-for="(item, i) in form.activityFileList" :key="i">
+                          <el-link v-if="item.canPreview" class="m-r-10" type="primary" @click="preview(item)">{{ item.fileName }}</el-link>
+                          <span v-else class="fileName">{{ item.fileName }}</span>
+                          <el-link type="primary" :underline="false" @click="download(item)">下载</el-link>
+                        </li>
+                      </ul>
+                    </template>
+                    
+                    <!-- 进展 -->
+                    <div class="l-title" id="part2"><img src="@/assets/img/label.png" alt=""> 项目进展</div>
+                    <ul class="progress" v-if="progress.length">
+                        <li v-for="(item,index) in progress" :key="index" :class="item.status == 0 ? 'not' : (item.status == 1 ? 'ing' : 'done')">
+                            <i class="dot"></i>
+                            <p class="name">{{item.title}}</p>
+                            <p class="desc">{{item.description}}</p>
+                        </li>
+                        <img class="rocket" src="@/assets/img/rocket.png" alt="">
+                    </ul>
+                    <template v-else>
+                        <div class="empty">
+                            <div>
+                                <img src="@/assets/img/none.png" alt="">
+                                <p>暂无数据</p>
+                            </div>
+                        </div>
+                    </template>
+                    
+                    <!-- 公告 -->
+                    <div class="l-title" id="part3"><img src="@/assets/img/label.png" alt=""> 通知公告</div>
+                    <ul class="notice-list" v-if="notices.length">
+                      <li v-for="(item, i) in notices" :key="i" @click="toNotice(item)">
+                        <h6>{{ item.announcementTitle }}</h6>
+                        <p class="meta">{{ item.updateTime }}</p>
+                        <div class="des" v-html="item.announcementText"></div>
+                      </li>
+                    </ul>
+                    <template v-else>
+                        <div class="empty">
+                            <div>
+                                <img src="@/assets/img/none.png" alt="">
+                                <p>暂无通知公告</p>
+                            </div>
+                        </div>
+                    </template>
+                  </div>
+              </div>
+          </div>
+        </div>
+        
+        <el-dialog title="报名" :visible.sync="peopleSignupVisible" :close-on-click-modal="false" width="300px">
+          <el-form class="dia-form">
+            <el-form-item>
+              <el-input placeholder="请输入4位数大赛邀请码" maxlength="4" v-model="peopleSignupForm.registrationInvitationCode"></el-input>
+            </el-form-item>
+          </el-form>
+          <span slot="footer" class="dialog-footer">
+            <el-button size="small" type="primary" @click="peopleSignupSubmit">报名</el-button>
+            <el-button size="small" @click="peopleSignupVisible = false">取消</el-button>
+          </span>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+import { mapState, mapMutations } from "vuex";
+import breadcrumb from '@/components/breadcrumb'
+import util from '@/libs/util'
+import Setting from "@/setting"
+import Const from '@/const/match'
+export default {
+    name: 'matchdetail',
+    data() {
+        return {
+            token: util.local.get(Setting.tokenKey),
+            id: +this.$route.query.id,
+            end: '',
+            status: '',
+            statusList: ["等待报名", "取消报名", "立即报名", "报名截止", "进入项目", "已结束"],
+            endList: ["报名开始", "报名截止", "报名截止", "项目开始", "项目结束", ""],
+            rules: Const.rules,
+            methods: Const.methods,
+            teamCalculationMethods: Const.teamCalculationMethods,
+            timerList: [],
+            form: {
+              founder: 1,
+              isOpen: 0, // 职站是否开启(0开启 1未开启 默认0)
+              maximumNumber: '',
+              carouselUrl: '',
+              coverUrl: '',
+              activityFileList: [], // 赛事附件
+              initiator: '',
+              isNeedCode: 0,
+              maximumNumber: 0,
+              signUpStartTime: '',
+              signUpEndTime: '',
+              playStartTime: '',
+              playEndTime: '',
+              projectDescribe: '',
+              projectName: '',
+              publishStatus: 0,
+            },
+            curType: '1',
+            typeList: [
+                {
+                    id: '1',
+                    name: '项目信息'
+                },
+                {
+                    id: '2',
+                    name: '项目进展'
+                },
+                {
+                    id: '3',
+                    name: '通知公告'
+                },
+            ],
+            progress: [],
+            timer: null,
+            notices: [],
+            noticeDetail: {},
+            curArch: '0',
+            arches: [],
+            ranks: [],
+
+            enterVisible: false,
+            enterForm: {
+              competitionId: this.$route.query.id,
+              teamId: '',
+              invitationCode: '',
+              registrationInvitationCode: '',
+              whetherSignUp: 1
+            },
+            
+            teamVisible: false,
+            teams: [],
+            teamNameRepeat: false,
+            teamForm: {
+              competitionId: this.$route.query.id,
+              registrationInvitationCode: '',
+              teamName: '',
+              invitationCode: '',
+              whetherSignUp: 1
+            },
+            curStage: null,
+            originInfo: {},
+            info: {
+              isCaption: 0,
+              person: {},
+              caption: {},
+              team: {
+                captain: 1,
+                invitationCode: ''
+              },
+              stages: [],
+              teamDetail: [],
+              teamInstructors: []
+            },
+            originIns: {
+              position: '',
+              name: '',
+              phone: '',
+            },
+            checkedPlayer: '',
+            transferVisible: false,
+            editing: false,
+            memberVisible: false,
+            members: [],
+            curRow: {},
+            chooseVisible: false,
+            checkedMember: '',
+            checkedMembers: [],
+            chooses: [],
+            lastType: 1,
+            playingStages: [],
+            peopleSignupVisible: false,
+            peopleSignupForm: {
+              registrationInvitationCode: ''
+            },
+        };
+    },
+    computed: {
+        ...mapState("user", [
+            'logView'
+        ]),
+    },
+    components: {
+        breadcrumb
+    },
+    mounted() {
+      this.$once('hook:beforeDestroy', function() {
+        clearInterval(this.timer)
+        this.timerList.forEach(n => {
+          clearTimeout(n)
+        })
+        this.timerList = []
+      })
+      this.getData()
+      // this.getProgress()
+      // this.getNotice()
+    },
+    methods: {
+        ...mapMutations('match', [
+          'SET_SOURCE'
+        ]),
+        getData() { // 获取项目信息
+            clearInterval(this.timer)
+            this.$post(`${this.api.findByIdActivity}?id=${this.id}`).then(({ data }) => {
+              const list = data.activityFileList
+              // 附件
+              if (list) {
+                list.map(e => {
+                  const { filePath } = e
+                  e.canPreview = util.canPreview(filePath.substr(filePath.lastIndexOf('.') + 1))
+                })
+              }
+
+              this.form = data
+              console.log("🚀 ~ file: index.vue:282 ~ this.$post ~ this.form:", this.form)
+              this.$refs.breadcrumb.update('全部赛事/' + data.projectName)
+              // this.handleStatus()
+            }).catch(err => {})
+        },
+        // 定时处理时间及状态
+        handleStatus() {
+          const { form } = this
+          let total = ''
+          let time = ''
+          let status = ''
+          let signUpStartTime = new Date(this.core.dateCompatible(form.signUpStartTime)) // 报名开始时间
+          let signUpEndTime = new Date(this.core.dateCompatible(form.signUpEndTime)) // 报名结束时间
+          let playStartTime = new Date(this.core.dateCompatible(form.playStartTime)) // 比赛开始时间
+          let playEndTime = new Date(this.core.dateCompatible(form.playEndTime)) // 比赛结束时间
+          this.timer = setInterval(() => {
+              const now = new Date()
+              if (now < signUpStartTime) { // 报名没开始
+                status = 0
+                total = signUpStartTime - now
+              } else if (now > signUpStartTime && now < signUpEndTime) { // 报名进行中
+                // 1已报名,2立即报名(没登录的情况下,直接显示立即报名,登录了则取报名信息,有则已报名,无则立即报名)
+                status = this.token ?
+                  (form.competitionRegistration ?
+                    1 :
+                    2) :
+                  2
+                total = signUpEndTime - now
+              } else if (now > signUpEndTime && now < playStartTime) { // 报名结束了,但比赛没开始
+                status = 3
+                total = playStartTime - now
+              } else if (now > playStartTime && now < playEndTime) { // 比赛进行中
+                // 如果是完整比赛
+                if (form.releaseType) {
+                  // 进行中的赛事,则遍历每个阶段的开始结束时间,看阶段比赛是否开始
+                  let curStage = null
+                  const stages = form.competitionStage
+                  if (stages) {
+                    this.playingStages = []
+                    form.competitionRegistration && stages.forEach(e => {
+                      if (now >= new Date(e.startTime) && now <= new Date(e.endTime) && e.method !== 2) this.playingStages.push(e)
+                    })
+                    let endText = ''
+                    for (const i in stages) {
+                      const e = stages[i]
+                      const startTime = new Date(e.startTime)
+                      const endTime = new Date(e.endTime)
+                      if (now < startTime) { // 阶段比赛未开始,不显示进入比赛按钮
+                        endText = '阶段开始'
+                        total = startTime - now
+                        break
+                      } else if (now >= startTime && now <= endTime && e.method !== 2) { // 阶段比赛进行中,显示进入比赛按钮
+                        if (form.competitionRegistration) { // 报名了才能进入比赛
+                          this.statusList[4] = e.count ? '已提交' : '进入' + e.stageName
+                          curStage = e
+                        } else if (!this.token) {
+                          this.statusList[4] = '进入' + e.stageName
+                          curStage = e
+                        }
+                        endText = '阶段结束'
+                        total = endTime - now
+                        break
+                      } else if (stages[i + 1] && now > endTime && now < new Date(stages[i + 1].startTime)) { // 过了该阶段的结束时间,但是没到下个阶段的开始时间,不显示进入比赛按钮
+                        endText = '阶段开始'
+                        total = new Date(stages[i + 1].startTime) - now
+                        break
+                      } else if (i === stages.length - 1) { // 当前时间在比赛开始结束时间之间,并且是最后一个阶段结束时间之后
+                        this.$set(form, 'stageName', '')
+                        endText = '项目结束'
+                        total = playEndTime - now
+                        break
+                      }
+                    }
+                    this.endList[4] = endText
+                  }
+                  this.curStage = curStage
+                } else { // 仅发布信息
+                  total = playEndTime - now
+                }
+                status = 4
+              } else if (now > playEndTime) { // 比赛结束
+                status = 5
+              }
+              this.status = status
+              total = total / 1000
+              --total
+              if (total > 86400) { // 超过一天则显示天数
+                // clearInterval(this.timer)
+                this.end = Math.floor(total / 86400) + '天'
+              } else if (total > 0) { // 一天之内,显示时分秒
+                let hours = Math.floor(total / (60 * 60))
+                let minutes = Math.floor(total % (60 * 60) / 60)
+                let seconds = Math.floor(total % (60 * 60) % 60)
+                time = `${this.core.formateTime(hours)}:${this.core.formateTime(minutes)}:${this.core.formateTime(seconds)}`
+                if (total > 0) this.end = time
+              } else if (this.status === 5) { // 项目结束,清除定时器
+                clearInterval(this.timer)
+              }
+          }, 1000)
+        },
+        getProgress() { // 获取项目进展
+            this.$get(this.api.getCompetitionProgress, {
+              competitionId: this.id
+            }).then(res => {
+                this.progress = res.competitionProgressList.reverse()
+            }).catch(err => {});
+        },
+        // 公告列表
+        getNotice() {
+          this.$post(`${this.api.queryAnnouncementByCompetitionId}?pageNum=1&pageSize=1000&competitionId=${this.id}`).then(({ data }) => {
+            const records = data.records.filter(e => e.status) // 只显示已发布的(status 0草稿 1为已发布)
+            records.map(e => {
+              e.announcementText = e.announcementText.replace(/<img.*?(?:>|\/>)/gi, '')
+            })
+            this.notices = records
+          }).catch(res => {})
+        },
+        // 预览附件
+        preview(item) {
+          const { filePath } = item
+          const suffix = filePath.substr(filePath.lastIndexOf('.') + 1)
+          window.open((util.isDoc(suffix) ? 'https://view.officeapps.live.com/op/view.aspx?src=' : '') + item.filePath)
+        },
+        // 下载附件
+        download(item) {
+          util.downloadFile(item.fileName, item.filePath)
+        },
+        // tab切换 前三个滚动,后两个切换
+        typeChange() {
+          const type = +this.curType
+          // 如果上个选中的是参赛信息,则检查修改数据后有没保存(团队名称、邀请码、指导老师)
+          if (this.lastType == 5) {
+            const { team, teamInstructors } = this.info
+            const { originInfo } = this
+            let notSave = 0
+            // 如果团队名称或者邀请码有修改
+            if (team.teamName !== originInfo.team.teamName || team.invitationCode !== originInfo.team.invitationCode) {
+              notSave = 1
+            } else if (JSON.stringify(teamInstructors) !== JSON.stringify(originInfo.teamInstructors)) {
+              notSave = 2
+            }
+            if (notSave) {
+              this.$confirm('所填写内容暂未保存,是否保存?', "提示", {
+                type: "warning"
+              }).then(() => {
+                // 保存团队名称和邀请码
+                if (notSave === 1) {
+                  this.edit()
+                } else { // 保存指导老师
+                  teamInstructors.map(e => {
+                    e.name && this.$post(this.api.addAnAdvisor, {
+                      name: e.name,
+                      competitionId: this.id,
+                      id: e.id,
+                      teamId: this.form.competitionRegistration ? this.form.competitionRegistration.teamId : '',
+                      phone: e.phone,
+                      position: e.position,
+                    }).then(res => {}).catch(res => {})
+                  })
+                }
+              }).catch(() => {})
+            } else {
+              type < 4 && document.querySelector(`#part${type}`).scrollIntoView()
+            }
+          } else {
+            type == 5 && this.getInfo()
+            type < 4 && document.querySelector(`#part${type}`).scrollIntoView()
+          }
+          this.editing = false
+          this.lastType = type
+        },
+        // 跳转公告详情
+        toNotice(item) {
+          this.$router.push(`noticeDetail?id=${item.id}&matchId=${this.id}&name=${this.form.name}&end=${this.end}&status=${this.status}`)
+        },
+        // 获取排名
+        getRank() {
+          const cur = +this.curArch
+          const data = {
+            pageNum: 1,
+            pageSize: 1000,
+            competitionId: this.id,
+            isOverallRanking: cur ? 0 : 1
+          }
+          data.stageIds = cur ? cur: ''
+          this.token && this.$post(this.api.frontOfficeCompetitionRanking, data).then(({ list }) => {
+            this.ranks = list
+          }).catch(res => {})
+        },
+        
+        // 删除指导老师
+        delAdvisor(row, i) {
+          if (row.id) {
+            this.$confirm('确定要删除吗?', '提示', {
+              type: 'warning'
+            }).then(() => {
+              this.$post(`${this.api.deleteAnAdvisor}?id=${row.id}`).then(res => {
+                util.successMsg('删除成功')
+                this.getInfo()
+              }).catch(res => {})
+            }).catch(() => {})
+          } else {
+            this.info.teamInstructors.splice(i, 1)
+          }
+        },
+        // 添加指导老师
+        addAdvisor() {
+          if (this.info.teamInstructors.length > 4) return util.errorMsg('指导老师仅限添加5个!')
+          this.info.teamInstructors.push(JSON.parse(JSON.stringify(this.originIns)))
+        },
+        // 编辑导老师
+        editAdvisor(row) {
+          this.$set(row, 'edit', 1)
+        },
+        // 提交指导老师
+        submitAdvisor(row) {
+          if (!row.name) return util.errorMsg('请输入姓名')
+          const { phone } = row
+          if (phone && !/^1[3456789]\d{9}$/.test(phone)) return util.errorMsg('请输入正确手机号格式')
+          this.$post(this.api.addAnAdvisor, {
+            name: row.name,
+            competitionId: this.id,
+            id: row.id,
+            teamId: this.form.competitionRegistration ? this.form.competitionRegistration.teamId : '',
+            phone: row.phone,
+            position: row.position,
+          }).then(res => {
+            util.successMsg((row.id ? '修改' : '新增') + '成功')
+            this.getInfo()
+          }).catch(res => {})
+        },
+        // 显示转让队长
+        transfer() {
+          // 取每个阶段的开始结束时间,有任何阶段开始了都不能转让队长和踢出队员
+          const now = new Date()
+          let start = 0
+          for (const e of this.form.competitionStage) {
+            if (now >= new Date(e.startTime) && now <= new Date(e.endTime)) {
+              util.errorMsg('比赛已经开始,无法转让队长!')
+              start = 1
+              break
+            }
+          }
+          if (!start) this.transferVisible = true
+        },
+        // 转让队长提交
+        transferSubmit() {
+          if (!this.checkedPlayer) return util.errorMsg('请选择成员')
+          this.$post(this.api.captainOfTransfer, {
+            captainId: this.info.caption.teamId,
+            playerId: this.checkedPlayer
+          }).then(res => {
+            this.checkedPlayer = ''
+            util.successMsg('转让成功')
+            this.transferVisible = false
+            this.getInfo()
+          }).catch(res => {})
+        },
+        // 踢出团队
+        removeLine(row) {
+          // 取每个阶段的开始结束时间,有任何阶段开始了都不能转让队长和踢出队员
+          const now = new Date()
+          let start = 0
+          for (const e of this.form.competitionStage) {
+            if (now >= new Date(e.startTime) && now <= new Date(e.endTime)) {
+              util.errorMsg('比赛已经开始,无法踢出成员!')
+              start = 1
+              break
+            }
+          }
+          if (!start) {
+            let include
+            for (const e of this.info.stages) {
+              if (e.participantAccountIds) {
+                const ids = e.participantAccountIds.split(',').map(n => +n)
+                if (ids.includes(row.accountId)) {
+                  include = e.stageName
+                  break
+                }
+              }
+            }
+            this.$confirm(include ? `该成员已被指定参加${include},踢出后需重新指定成员参加,是否确认踢出团队?` : '确定要踢出该成员吗?', '提示', {
+              type: 'warning'
+            }).then(() => {
+              this.$post(`${this.api.removeTheLine}?teamId=${this.info.teamId}&competitionId=${this.id}&accountId=${row.accountId}`).then(res => {
+                util.successMsg('移除成功')
+                this.getInfo()
+              }).catch(res => {})
+            }).catch(() => {})
+          }
+        },
+        // 移除参赛人员
+        removePar(e, stage) {
+          this.$confirm('确定要移除该成员吗?', '提示', {
+            type: 'warning'
+          }).then(() => {
+            this.$post(this.api.cancelParticipant, {
+              accountId: e.id,
+              competitionId: this.id,
+              stageId: stage.stageId,
+              teamId: this.info.teamId
+            }).then(res => {
+              util.successMsg('移除成功')
+              this.getInfo()
+            }).catch(res => {})
+          }).catch(() => {})
+        },
+        // 选择参赛人员
+        selectPar(row) {
+          const item = this.form.competitionStage.find(e => e.stageId == row.stageId)
+          if (item) {
+            // 该阶段已经开始比赛则不能修改
+            const now = new Date()
+            if (now >= new Date(item.startTime) && now <= new Date(item.endTime)) {
+              return util.errorMsg('该阶段比赛已经开始,无法修改允许参赛人员!')
+            } else {
+              const { teamLimit, stages, teamDetail } = this.info
+              // teamLimit=true,则每个成员只能参加一个阶段的比赛,要获取stages里返回的所有participantAccountIds(参赛人员的accountId),然后不显示这些参赛人员
+              if (teamLimit) {
+                const chooses = []
+                let ids = []
+                // 获取已经允许参赛的人员accountId
+                stages.map(e => {
+                  const id = e.participantAccountIds
+                  if (e.stageId != row.stageId && id) ids.push(...id.split(',').map(n => +n))
+                })
+                ids = [...new Set(ids)]
+                teamDetail.map(e => {
+                  ids.includes(e.accountId) || chooses.push(e) // 没有参赛的人员则显示出来
+                })
+                this.chooses = chooses
+              } else {
+                this.chooses = this.info.teamDetail
+              }
+              this.curRow = row
+              this.checkedMembers = row.participantAccountIds ? row.participantAccountIds.split(',').map(e => +e) : [] // 选中了的人员要回显
+              this.chooseVisible = true
+            }
+          }
+        },
+        // 选择参赛人员提交
+        chooseSubmit() {
+          const accountIds = this.checkedMembers
+          if (!accountIds.length) return util.errorMsg('请选择参赛成员!')
+          const limit = this.curRow.teamNumLimit // 参赛人数限制
+          if (limit && accountIds.length > limit) return util.errorMsg(`请选择${limit}个以下参赛成员!`) // 选择的参赛人员个数不能大于参赛人数限制
+          this.$post(this.api.stageSelectParticipants, {
+            accountIds,
+            competitionId: this.id,
+            stageId: this.curRow.stageId,
+            teamId: this.info.teamId
+          }).then(res => {
+            this.checkedMembers = []
+            util.successMsg('修改成功')
+            this.getInfo()
+            this.chooseVisible = false
+          }).catch(res => {})
+        },
+        // 查看成绩详情
+        show(row) {
+          // 团队展示弹框,个人跳转实验报告
+          if (this.form.completeCompetitionSetup.competitionType) { // 团队比赛则展示团队成员成绩详情
+            this.curRow = row
+            this.memberVisible = true
+            const teamId = this.form.competitionRegistration.teamId
+            if (teamId) {
+              this.$post(this.api.stageTeamScoreDetails, {
+                pageNum: 1,
+                pageSize: 1000,
+                competitionId: this.id,
+                stageId: row.stageId,
+                teamId
+              }).then(({ page }) => {
+                this.members = page.records
+              }).catch(res => {})
+            } else {
+              this.members = []
+            }
+          } else if (row.reportId) { // 个人比赛,并且有reportId,则进入实验报告
+            this.toReport(row)
+          }
+        },
+        // 跳转实验报告
+        toReport(row) {
+          this.$router.push(`/record/show?reportId=${row.reportId}&matchId=${this.id}&matchName=${this.form.name}`)
+        },
+        
+        // 个人报名提交
+        peopleSignupSubmit() {
+          this.$post(this.api.addCompetitionRegistration, {
+            competitionId: this.id,
+            registrationInvitationCode: this.peopleSignupForm.registrationInvitationCode
+          }).then(res => {
+            this.peopleSignupVisible = false
+            this.getData()
+            this.$message.success('报名成功')
+          }).catch(res => {})
+        },
+        // 团队报名提交
+        enterSubmit() {
+          const form = this.enterForm
+          if (!form.teamId) return util.errorMsg('请选择团队')
+          if (!form.invitationCode) return util.errorMsg('请输入团队邀请码')
+          if (this.form.completeCompetitionSetup.isNeedCode && !form.registrationInvitationCode) return util.errorMsg('请输入大赛邀请码')
+          this.$post(this.api.joinCompetitionTeam, form).then(({ status, data, message }) => {
+            this.enterVisible = false
+            this.getData()
+            util.successMsg('报名成功!')
+          }).catch(res => {})
+        },
+        // 团队关闭
+        enterClose() {
+          this.enterForm = {
+            competitionId: this.id,
+            teamId: '',
+            registrationInvitationCode: '',
+            invitationCode: '',
+            whetherSignUp: 1
+          }
+        },
+
+        
+        // 创建团队
+        toTeam() {
+          this.teamVisible = true
+        },
+        // 获取团队列表
+        getTeam() {
+          this.$get(this.api.searchTeam, {
+            teamName: '',
+            competitionId: this.id
+          }).then(({ teamList }) => {
+            this.teams = teamList
+          }).catch(res => {})
+        },
+        // 团队提交
+        teamSubmit() {
+          const form = this.teamForm
+          if (!form.teamName) return util.errorMsg('请输入团队名称')
+          if (this.teamNameRepeat) return util.errorMsg('团队名称重复,请重新输入')
+          if (form.invitationCode.length !== 6) return util.errorMsg('请输入6位数团队邀请码')
+          if (this.form.completeCompetitionSetup.isNeedCode && !form.registrationInvitationCode) return util.errorMsg('请输入大赛邀请码')
+          this.$post(this.api.addCompetitionTeam, form).then(({ status, data, message }) => {
+            this.teamVisible = false
+            this.enterVisible = false
+            this.getData()
+            util.successMsg('报名成功!')
+          }).catch(res => {})
+        },
+        // 团队关闭
+        teamClose() {
+          this.teamForm = {
+            competitionId: this.id,
+            teamName: '',
+            invitationCode: '',
+            registrationInvitationCode: '',
+            whetherSignUp: 1
+          }
+        },
+
+        // 选择要进入的阶段
+        chooseStage(e) {
+          this.curStage = e
+          this.signup()
+        },
+        // 判断是否能进赛事
+        getAllow() {
+          // 是否允许参加赛事(淘汰赛制)
+          if (this.form.rule === 1) {
+            this.$post(this.api.allowedParticipateCompetition, {
+              competitionId: this.id,
+              number: this.curStage.number,
+              stageId: this.curStage.stageId,
+              teamId: this.form.competitionRegistration.teamId,
+            }).then(res => {
+              this.toSub()
+            }).catch(res => {})
+          } else {
+            this.toSub()
+          }
+        },
+        // 立即报名
+        signup(){
+          const { status, form } = this
+          // 如果登录了
+          if (util.local.get(Setting.tokenKey)) {
+            const { competitionType } = form.completeCompetitionSetup
+            if (status == 4) { // 进入比赛
+              // 参加过比赛不让参加
+              if (this.curStage && this.curStage.count) return util.errorMsg('您已经参加过该阶段项目!')
+              if (form.competitionRegistration.isDisable === 1) return util.errorMsg('当前用户已被禁赛,如有疑问,请联系平台管理员。') // 被禁用的用户不能进入大赛
+              // 团队赛,则判断是否为参赛人员
+              if (competitionType) {
+                this.$post(this.api.isParticipant, {
+                  competitionId: this.id,
+                  stageId: this.curStage.stageId,
+                  teamId: form.competitionRegistration.teamId,
+                }).then(res => {
+                  this.getAllow()
+                }).catch(res => {})
+              } else {
+                this.getAllow()
+              }
+            } else if (status == 2) { // 报名
+              // 团队赛报名
+              if (competitionType) {
+                this.enterVisible = true
+              } else { // 个人赛报名
+                if (form.completeCompetitionSetup.isNeedCode) {
+                  this.peopleSignupForm.registrationInvitationCode = ''
+                  this.peopleSignupVisible = true
+                } else {
+                  this.$post(this.api.addCompetitionRegistration, {
+                    competitionId: this.id
+                  }).then(res => {
+                    this.getData()
+                    this.$message.success('报名成功')
+                  }).catch(res => {})
+                }
+              }
+            } else if (status == 1) {
+              // 已报名,点击取消报名
+              this.$confirm('是否要取消报名?', '提示', {
+                type: 'success'
+              }).then(() => {
+                this.$post(`${this.api.cancelRegistration}?competitionId=${this.id}`).then(res => {
+                  this.$message.success('取消报名成功')
+                  this.getData()
+                }).catch(res => {})
+              }).catch(() => {})
+            }
+          } else { // 如果没登录,提示去登录
+            this.$confirm('请先登录,是否直接前往登录?', "提示", {
+              type: 'success'
+            }).then(() => {
+              this.SET_SOURCE(this.id)
+              this.$router.push('/login')  
+            }).catch(() => {})
+          }
+        },
+        // 进入python系统
+        toPython() {
+          const form = this.curStage
+          let token = util.local.get(Setting.tokenKey);
+          util.cookies.set('assessmentId', '', -1)
+          util.cookies.set('startTime', '', -1)
+          util.cookies.set('stopTime', '', -1)
+          util.cookies.set('projectId', form.projectId)
+          util.cookies.set('token', token)
+          util.cookies.set('courseId', form.cid)
+          util.cookies.set('curriculumName', escape(form.systemName))
+          util.cookies.set('systemId', form.systemId)
+          util.cookies.set('competitionId', this.form.id)
+          util.cookies.set('stageId', form.stageId)
+          util.cookies.set('teamId', this.form.competitionRegistration.teamId)
+          util.cookies.set('stopTime', form.endTime)
+          util.cookies.set('resultsDetails', form.resultsDetails)
+          util.cookies.set('resultAnnouncementTime', form.resultAnnouncementTime)
+          util.cookies.set('fromManager', '', -1)
+          // 8个python子系统都跳这个地址,子系统会通过cookie里的systemId识别展示哪套系统
+          location.href = process.env.NODE_ENV === 'development' ?
+            `http://${location.hostname}:8085/#/` :
+            Setting.isPro ?
+              `https://${location.hostname}/pyTrials` :
+              `${location.origin}/pyTrials`
+        },
+        // 进入子系统
+        toSub() {
+          const { form } = this
+          const { systemId, projectId, cid, stageId } = this.curStage
+          const competitionId = form.id
+          const teamId = form.competitionRegistration.teamId
+          let token = util.local.get(Setting.tokenKey);
+          if (systemId == 11) {
+              // 银行系统
+              location.href = `${Setting.systemPath}/#/index/list?curriculumName=${this.curriculumName}&token=${token}&cid=${cid}&systemId=${systemId}&projectId=${projectId}&competitionId=${competitionId}&stageId=${stageId}&teamId=${teamId}&assessmentId=&classId=&stopTime=&test=true`
+          } else if (systemId == 12) {
+              // 众筹系统
+              window.open(`http://120.78.139.126:8879?systemId=${systemId}&courseId=${cid}&projectId=${projectId}&token=${token}&userId=${this.userId}&classId=1&competitionId=${competitionId}&stageId=${stageId}&teamId=${teamId}`);
+          } else {
+            // python系统
+            this.toPython(this.curProject)
+          }
+        }
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+.banner{
+    width: 100%;
+    height: 350px;
+    padding: 120px 0 0 20%;
+    color: #fff;
+    background-size: 100% 350px;
+    background-repeat: no-repeat;
+}
+.l-title {
+  font-size: 18px;
+}
+.main .center-con {
+    background: url(../../../assets/img/match-bg1.png) (0px 95px)/auto auto no-repeat,
+                url(../../../assets/img/match-bg2.png) (98% 300px)/auto auto no-repeat;
+}
+.main .center-wrap {
+    margin-top: 30px;
+}
+.rule-title {
+  margin-bottom: 10px;
+  font-size: 16px;
+}
+.rule {
+  padding: 15px;
+  margin-bottom: 15px;
+  border: 1px solid #dfdfdf;
+  p {
+    font-size: 14px;
+    line-height: 30px;
+    color: #6e6e6e;
+  }
+}
+/deep/.el-tabs__item {
+  box-shadow: none !important;
+}
+.content{
+    position: relative;
+    padding: 20px 40px;
+    margin-top: 30px;
+    background-color: #fff;
+    .title{
+        width: 67%;
+        margin: 0 auto;
+        font-size: 28px;
+        text-align: center;
+        color: #0B1D30;
+    }
+    .tool {
+      z-index: 100;
+      position: sticky;
+      top: 64px;
+      margin-bottom: 20px;
+      background-color: #fff;
+      &.logView {
+        z-index: 0;
+      }
+    }
+    .info .meta{
+        padding: 16px 0;
+        font-size: 12px;
+        color: #999;
+        text-align: center;
+    }
+    .action {
+        display: inline-flex;
+        align-items: center;
+    }
+    .status {
+        max-width: 120px;
+        padding: 0 16px;
+        margin-left: 20px;
+        line-height: 34px;
+        font-size: 14px;
+        color: #fff;
+        background-color: #52C41A;
+        border-radius: 4px;
+        cursor: pointer;
+        @include ellipsis();
+        &.wait {
+            background-color: #FAAD14;
+        }
+        &.signing {
+            background-color: $main-color;
+        }
+        &.signed {
+            background-color: #52C41A;
+        }
+        &.playing {
+            background-color: #f96d6d;
+        }
+        &.finish {
+            background-color: #ccc;
+        }
+    }
+    .end-text {
+        font-size: 12px;
+        color: #666;
+        em {
+            font-style: normal;
+            color: #f00;
+        }
+    }
+    .texts{
+        margin: 20px 0 50px;
+        font-size: 14px;
+        line-height: 1.6;
+        text-indent: 2em;
+        overflow: hidden;
+        /deep/img{
+            max-width: 100%;
+        }
+    }
+    .progress{
+        position: relative;
+        width: 95%;
+        padding: 50px 0;
+        margin: 40px auto 80px;
+        text-align: left;
+        &:before{
+            content: '';
+            position: absolute;
+            top: 0;
+            left: 50%;
+            width: 2px;
+            height: 100%;
+            background-color: #E1E6F2;
+        }
+        &:after {
+            content: '';
+            position: absolute;
+            top: -10px;
+            left: 430px;
+            border: 8px solid transparent;
+            border-bottom-color: #E1E6F2;
+        }
+        .rocket {
+            position: absolute;
+            bottom: -50px;
+            left: 425px;
+        }
+        li{
+            position: relative;
+            width: 400px;
+            margin-bottom: 42px;
+            .dot{
+                position: absolute;
+                top: 12px;
+                left: 431px;
+                width: 15px;
+                height: 15px;
+                background-color: #DCDCDC;
+                border-radius: 50%;
+            }
+            .name{
+                display: inline-block;
+                padding: 0 19px;
+                margin-bottom: 16px;
+                line-height: 40px;
+                text-align: center;
+                font-size: 16px;
+                color: #fff;
+                border-radius: 20px;
+                background-color: #C4C4C4;
+            }
+            .desc{
+                position: relative;
+                color: #333;
+                font-size: 14px;
+            }
+            &.ing, &.done {
+                .dot {
+                    top: 8px;
+                    background-color: #007EFF;
+                }
+                .name {
+                    background-color: #007EFF;
+                }
+            }
+            &.ing {
+                .dot {
+                    width: 27px;
+                    height: 27px;
+                    border: 6px solid #E2F1FB;
+                }
+            }
+            &:nth-child(odd) {
+                text-align: right;
+                &.ing {
+                    .dot {
+                        left: auto;
+                        right: -51px;
+                    }
+                }
+                .name {
+                    &:before {
+                        content: '';
+                        z-index: 2;
+                        position: absolute;
+                        top: 14px;
+                        right: -35px;
+                        border: 18px solid transparent;
+                        border-top-width: 6px;
+                        border-bottom-width: 6px;
+                        border-left-color: #C4C4C4;
+                    }
+                }
+                .desc {
+                    text-align: right;
+                }
+                &.ing, &.done {
+                    .name {
+                        &:before {
+                            border-left-color: #007EFF;
+                        }
+                    }
+                }
+            }
+            &:nth-child(even) {
+                margin-left: 482px;
+                .dot {
+                    left: -51px;
+                }
+                &.ing {
+                    .dot {
+                        left: -57px;
+                    }
+                }
+                .name {
+                    text-align: left;
+                    &:after {
+                        content: '';
+                        z-index: 2;
+                        position: absolute;
+                        top: 14px;
+                        left: -35px;
+                        border: 18px solid transparent;
+                        border-top-width: 6px;
+                        border-bottom-width: 6px;
+                        border-right-color: #C4C4C4;
+                    }
+                }
+                .desc {
+                    &:before {
+                        left: auto;
+                        right: -16px;
+                        border: 8px solid transparent;
+                        border-left-color: #fff;
+                    }
+                    &:after {
+                        left: auto;
+                        right: -18px;
+                        border: 9px solid transparent;
+                        border-left-color: #E6E6E6;
+                    }
+                }
+                
+                &.ing, &.done {
+                    .name {
+                        &:after {
+                            border-right-color: #007EFF;
+                        }
+                    }
+                }
+            }
+            &:last-child{
+                margin-bottom: 0;
+            }
+        }
+    }
+}
+.files {
+  margin-bottom: 30px;
+  li {
+    display: flex;
+    align-items: center;
+    margin: 10px 0;
+  }
+  .fileName {
+    margin-right: 10px;
+    font-size: 12px;
+  }
+}
+.notice-list {
+  text-align: left;
+  li {
+    padding: 16px;
+    margin-bottom: 12px;
+    transition: all 0.3s;
+    cursor: pointer;
+    border-radius: 6px;
+    background-color: #fff;
+    border-bottom: 1px dashed #ebebeb;
+    &:last-child {
+      border-bottom: 0;
+    }
+  }
+  h6 {
+    font-size: 20px;
+    font-weight: 500;
+    color: #0B1D30;
+    &:hover {
+      color: #007EFF;
+    }
+  }
+  .meta {
+    margin: 10px 0;
+    font-size: 14px;
+    color: #666;
+  }
+  .des {
+    font-size: 14px;
+    color: #333;
+    line-height: 24px;
+    display: -webkit-box;
+    display:-moz-box;
+    -webkit-box-orient: vertical;
+    -moz-box-orient: vertical;
+    -webkit-line-clamp: 2;
+    -moz-line-clamp: 2;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+}
+.table {
+  width: 100%;
+  border-collapse: collapse;
+  th, td {
+    padding: 12px;
+    border: 1px solid #ebeef5;
+  }
+  &.tc {
+    text-align: center;
+  }
+  th {
+    text-align: center;
+    background-color: #f8faff;
+  }
+  .icon {
+    margin-right: 10px;
+    font-size: 16px;
+    color: #7a7a7a;
+    cursor: pointer;
+    &:hover {
+      color: #007EFF;
+    }
+  }
+  .plus {
+    margin-bottom: 10px;
+    text-align: right;
+  }
+  .line {
+    display: flex;
+    align-items: center;
+    margin-bottom: 10px;
+    .el-input {
+      margin-right: 15px;
+    }
+  }
+}
+.flex-center {
+  display: flex;
+  justify-content: space-between;
+  align-items: center;
+  margin-bottom: 15px;
+}
+/deep/.dia-form {
+  .w-100 {
+    width: 100%;
+  }
+  .tips {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/activity/list/index.vue b/src/pages/activity/list/index.vue
new file mode 100644
index 0000000..41584b2
--- /dev/null
+++ b/src/pages/activity/list/index.vue
@@ -0,0 +1,961 @@
+<template>
+    <div class="wrap index">
+        <div class="search">
+            <h6>赛事竞技,精彩纷呈</h6>
+            <div class="input">
+                <img src="@/assets/img/search.png" alt="">
+                <input type="text" placeholder="请输入关键词" v-model="keyword">
+            </div>
+        </div>
+
+        <div class="main">
+            <div class="center-wrap list-inner">
+                <ul v-if="token" class="nav">
+                    <li :class="{ active: form.eventType === item.id }" v-for="(item, index) in typeList" :key="index" @click="changeType(item.id)">{{ item.name }}
+                    </li>
+                </ul>
+                <div class="list-wrap">
+                  <!-- 课程筛选 -->
+                  <div class="filter">
+                      <div>
+                          <dl>
+                              <dt>筛选排序:</dt>
+                              <dd v-for="(item, i) in sorts" :key="i" :class="{active: form.sequence == item.id}" @click="changeSort(item.id)">{{ item.name }}</dd>
+                          </dl>
+                      </div>
+                      <el-button type="primary" @click="$router.push('manage')">我的项目</el-button>
+                  </div>
+
+                    <div class="list">
+                        <template v-if="listData.length">
+                            <ul>
+                                <li v-for="(item,index) in listData" :key="index" @click="toDetail(item)">
+                                    <div class="left">
+                                        <!-- <el-button v-if="item.status === 1 || item.status === 2 || item.curStage" :class="['stage-label', {playing: item.curStage}]" type="primary">{{ item.status === 1 || item.status === 2 ? '报名中' : item.curStage ? '竞赛中' : '' }}</el-button> -->
+                                        <div class="cover">
+                                            <img :src="item.coverUrl || 'https://huoran.oss-cn-shenzhen.aliyuncs.com/20220623/png/1539857403162943488.png'" alt="">
+                                        </div>
+                                        <div class="info">
+                                            <div class="title">{{ item.projectName }}</div>
+                                            <div class="metas">
+                                                <div>
+                                                    <span class="label">报名时间:</span><span class="val">{{ item.signUpStartTime}} ~ {{ item.signUpEndTime }}</span>
+                                                </div>
+                                                <div>
+                                                    <span class="label">项目时间:</span><span class="val">{{ item.playStartTime}} ~ {{ item.playEndTime }}</span>
+                                                </div>
+                                              <template v-if="item.initiator">
+                                                <div :class="{'flex-top': item.initiator.split(',').length > 1}">
+                                                    <span class="label">发起方:&emsp;</span>
+                                                    <span class="val">{{ item.initiator }}</span>
+                                                </div>
+                                              </template>
+                                            </div>
+                                        </div>
+                                    </div>
+                                    <div class="right">
+                                        <el-dropdown v-if="item.playingStages && item.playingStages.length > 1" class="m-l-10" @click.stop="stageClick" @command="e => chooseStage(e, item)">
+                                          <el-button type="primary" style="background-color: #f96d6d;border: 0;">
+                                            选择竞赛<i class="el-icon-arrow-down el-icon--right"></i>
+                                          </el-button>
+                                          <el-dropdown-menu slot="dropdown">
+                                            <el-dropdown-item v-for="(stage, i) in item.playingStages" :key="i" :command="stage">进入{{ stage.stageName }}</el-dropdown-item>
+                                          </el-dropdown-menu>
+                                        </el-dropdown>
+                                        <p class="status"
+                                        v-else-if="item.status != 4 || (item.status == 4 && item.curStage)"
+                                        :class="{wait: item.status == 0,signing: item.status == 2,signed: item.status == 1,playing: item.status == 4 && item.curStage,finish: item.status == 3 || item.status == 5}" :title="item.status == 4 ? item.stageName : statusList[item.status]"
+                                        @click.stop="signup(item)">{{ item.status == 4 ? item.stageName : statusList[item.status] }}</p>
+                                        <p class="end-text" v-if="item.end">
+                                            距离{{ item.status == 4 ? item.endText : endList[item.status] }}还有
+                                            <em >{{ item.end }}</em>
+                                        </p>
+                                    </div>
+                                </li>
+                            </ul>
+                            <div class="pagination">
+                                <el-pagination background layout="total, prev, pager, next" :total="totals"
+                                            @current-change="handleCurrentChange"
+                                            :current-page="page">
+                                </el-pagination>
+                            </div>
+                        </template>
+                        <template v-else>
+                            <div class="empty">
+                                <div>
+                                    <img src="@/assets/img/none.png" alt="">
+                                    <p>暂无赛事</p>
+                                </div>
+                            </div>
+                        </template>
+                    </div>
+                </div>
+            </div>
+        </div>
+
+        
+        <el-dialog title="报名" :visible.sync="peopleSignupVisible" :close-on-click-modal="false" width="300px">
+          <el-form class="dia-form">
+            <el-form-item>
+              <el-input placeholder="请输入4位数大赛邀请码" maxlength="4" v-model="peopleSignupForm.registrationInvitationCode"></el-input>
+            </el-form-item>
+          </el-form>
+          <span slot="footer" class="dialog-footer">
+            <el-button size="small" type="primary" @click="peopleSignupSubmit">报名</el-button>
+            <el-button size="small" @click="peopleSignupVisible = false">取消</el-button>
+          </span>
+        </el-dialog>
+        <el-dialog title="报名" :visible.sync="enterVisible" :close-on-click-modal="false" width="300px">
+          <el-form class="dia-form">
+            <p style="margin-bottom: 5px">请选择要加入的团队</p>
+            <el-form-item>
+              <el-select class="w-100" v-model="enterForm.teamId" filterable>
+                <el-option v-for="(item, i) in teams" :key="i" :label="item.teamName" :value="item.teamId"></el-option>
+              </el-select>
+            </el-form-item>
+            <el-form-item>
+              <el-input placeholder="请输入6位数团队邀请码" maxlength="6" v-model="enterForm.invitationCode"></el-input>
+            </el-form-item>
+            <el-form-item v-if="curItem.setup.isNeedCode">
+              <el-input placeholder="请输入4位数大赛邀请码" maxlength="4" v-model="enterForm.registrationInvitationCode"></el-input>
+            </el-form-item>
+            <p class="tips">
+              查找不到团队?点击 <el-link :underline="false" type="primary" @click="toTeam">创建团队</el-link>
+            </p>
+          </el-form>
+          <span slot="footer" class="dialog-footer">
+            <el-button size="small" type="primary" @click="enterSubmit">报名</el-button>
+            <el-button size="small" @click="enterVisible = false">取消</el-button>
+          </span>
+        </el-dialog>
+        <el-dialog title="创建团队" :visible.sync="teamVisible" :close-on-click-modal="false" width="300px">
+          <el-form class="dia-form">
+            <el-form-item>
+              <el-input placeholder="请输入团队名称" maxlength="10" v-model="teamForm.teamName"></el-input>
+            </el-form-item>
+            <el-form-item>
+              <el-input placeholder="请输入6位数团队邀请码" maxlength="6" v-model="teamForm.invitationCode"></el-input>
+            </el-form-item>
+            <el-form-item v-if="curItem.setup.isNeedCode">
+              <el-input placeholder="请输入4位数大赛邀请码" maxlength="4" v-model="teamForm.registrationInvitationCode"></el-input>
+            </el-form-item>
+          </el-form>
+          <span slot="footer" class="dialog-footer">
+            <el-button size="small" type="primary" @click="teamSubmit">创建并报名</el-button>
+            <el-button size="small" @click="teamVisible = false">取消</el-button>
+          </span>
+        </el-dialog>
+    </div>
+</template>
+
+<script>
+import { mapState, mapMutations } from "vuex";
+import { Loading } from "element-ui";
+import Setting from "@/setting"
+import util from "@/libs/util"
+
+export default {
+    name: "match",
+    data() {
+        return {
+            timer: null,
+            redisTimer: null,
+            token: util.local.get(Setting.tokenKey),
+            statusList: ["待报名", "取消报名", "马上报名", "报名截止", "进入初赛", "已结束"],
+            endList: ["报名开始", "报名截止", "报名截止", "竞赛开始", "竞赛结束", ""],
+            typeList: [
+                {
+                    id: '',
+                    name: "本校项目"
+                },
+                {
+                    id: 0,
+                    name: "已报名"
+                }
+            ],
+            provinces: [],
+            cities: [],
+            form: {
+              sequence: 2, // 排序(1:近期排名 2.最近更新)
+              eventType: ''
+            },
+            squareScopes: [
+              {
+                id: 3,
+                name: '不限'
+              },
+              {
+                id: 1,
+                name: '全平台'
+              },
+              {
+                id: 2,
+                name: '指定区域/院校'
+              }
+            ],
+            scopes: [
+              {
+                id: 3,
+                name: '不限'
+              },
+              {
+                id: 0,
+                name: '本校内'
+              },
+              {
+                id: 1,
+                name: '全平台'
+              },
+              {
+                id: 2,
+                name: '指定区域/院校'
+              }
+            ],
+            sorts: [
+              {
+                id: 2,
+                name: '最近更新'
+              },
+              {
+                id: 1,
+                name: '近期报名'
+              }
+            ],
+            sort: 1,
+            keyword: "",
+            searchTimer: null,
+            page: 1,
+            pageSize: 10,
+            totals: 0,
+            listData: [],
+            covers: [],
+            loadIns: null,
+            contestIds: [],
+            timerList: [],
+
+            
+            enterVisible: false,
+            enterForm: {
+              competitionId: '',
+              teamId: '',
+              invitationCode: '',
+              registrationInvitationCode: '',
+              whetherSignUp: 1
+            },
+            
+            teamVisible: false,
+            teams: [],
+            teamNameRepeat: false,
+            teamForm: {
+              competitionId: '',
+              registrationInvitationCode: '',
+              teamName: '',
+              invitationCode: '',
+              whetherSignUp: 1
+            },
+            curItem: {
+              setup: {}
+            },
+            peopleSignupVisible: false,
+            peopleSignupForm: {
+              registrationInvitationCode: ''
+            },
+            curRow: {}
+        };
+    },
+    computed: {
+        ...mapState('match', [
+          'eventType'
+        ])
+    },
+    watch: {
+        keyword: function(val) {
+            clearTimeout(this.searchTimer);
+            this.searchTimer = setTimeout(() => {
+                this.initData();
+            }, 500);
+        }
+    },
+    mounted() {
+      this.getData()
+      this.$once('hook:beforeDestroy', function() {
+        this.clearTimer()
+        clearInterval(this.redisTimer)
+      })
+    },
+    methods: {
+        ...mapMutations('match', [
+          'SET_TYPE'
+        ]),
+        getList() {
+          this.clearTimer()
+          const { form } = this
+          const { eventType, competitionScope } = form
+          const data = {
+            pageNum: this.page,
+            pageSize: this.pageSize,
+            platformSource: 2,
+            keyWords: this.keyword,
+            ...this.form
+          }
+          this.$post(this.api.schoolActivities, data).then(({ data }) => {
+            const { records } = data
+            this.listData = records
+            this.totals = data.total
+            // this.handleStatus()
+            // this.loadIns.close();
+          }).catch(res => {
+            // this.loadIns.close()
+          })
+        },
+        // 定时处理时间及状态
+        handleStatus() {
+          this.listData.map(item => {
+            if (item.signUpStartTime && item.signUpEndTime && item.playStartTime && item.playEndTime) {
+              let total = ''
+              let time = ''
+              let status = ''
+              let signUpStartTime = new Date(this.core.dateCompatible(item.signUpStartTime)) // 报名开始时间
+              let signUpEndTime = new Date(this.core.dateCompatible(item.signUpEndTime)) // 报名结束时间
+              let playStartTime = new Date(this.core.dateCompatible(item.playStartTime)) // 比赛开始时间
+              let playEndTime = new Date(this.core.dateCompatible(item.playEndTime)) // 比赛结束时间
+              let timer = setInterval(() => {
+                  const now = new Date()
+                  if (now < signUpStartTime) { // 报名没开始
+                    status = 0
+                    total = signUpStartTime - now
+                  } else if (now > signUpStartTime && now < signUpEndTime) { // 报名进行中
+                    // whetherToSignUp 0已报名,1未报名
+                    status = item.whetherToSignUp ? 2 : 1 // 1已报名,2立即报名
+                    total = signUpEndTime - now
+                  } else if (now > signUpEndTime && now < playStartTime) { // 报名结束了,但比赛没开始
+                    status = 3
+                    total = playStartTime - now
+                  } else if (now > playStartTime && now < playEndTime) { // 比赛进行中
+                    // 如果是完整比赛
+                    if (item.releaseType) {
+                      // 进行中的赛事,则遍历每个阶段的开始结束时间,看阶段比赛是否开始
+                      let curStage = null
+                      const stages = item.competitionStageList
+                      if (stages) {
+                        item.playingStages = []
+                        // 报了名才算作可以进入的阶段
+                        item.whetherToSignUp === 0 && stages.forEach(e => {
+                          if (now >= new Date(e.startTime) && now <= new Date(e.endTime) && e.method !== 2) item.playingStages.push(e)
+                        })
+                        for (const i in stages) {
+                          const e = stages[i]
+                          const startTime = new Date(e.startTime)
+                          const endTime = new Date(e.endTime)
+                          if (now < startTime) { // 阶段比赛未开始,不显示进入比赛按钮
+                            this.$set(item, 'stageName', '')
+                            this.$set(item, 'endText', '阶段开始')
+                            total = startTime - now
+                            break
+                          } else if (now >= startTime && now <= endTime) { // 阶段比赛进行中,显示进入比赛按钮
+                            if (item.whetherToSignUp === 0 && e.method !== 2) this.$set(item, 'stageName', e.count ? '已提交' : '进入' + e.stageName) // 报名了并且没参加比赛才能进入比赛
+                            this.$set(item, 'endText', '阶段结束')
+                            curStage = e
+                            total = endTime - now
+                            break
+                          } else if (stages[i + 1] && now > endTime && now < new Date(stages[i + 1].startTime)) { // 过了该阶段的结束时间,但是没到下个阶段的开始时间,不显示进入比赛按钮
+                            this.$set(item, 'stageName', '')
+                            this.$set(item, 'endText', '阶段开始')
+                            total = new Date(stages[i + 1].startTime) - now
+                            break
+                          } else if (i === stages.length - 1) { // 当前时间在比赛开始结束时间之间,并且是最后一个阶段结束时间之后
+                            this.$set(item, 'stageName', '')
+                            this.$set(item, 'endText', '竞赛结束')
+                            total = playEndTime - now
+                            break
+                          }
+                        }
+                      }
+                      item.curStage = curStage
+                    } else { // 仅发布信息
+                      this.$set(item, 'endText', '竞赛结束')
+                      total = playEndTime - now
+                    }
+                    status = 4
+                  } else if (now > playEndTime) { // 比赛结束
+                    status = 5
+                  }
+                  this.$set(item, 'status', status)
+                  total = total / 1000
+                  --total
+                  if (total > 86400) { // 超过一天则显示天数
+                    // clearInterval(timer)
+                    this.$set(item, 'end', Math.floor(total / 86400) + '天')
+                  } else if (total > 0) { // 一天之内,显示时分秒
+                      let hours = Math.floor(total / (60 * 60))
+                      let minutes = Math.floor(total % (60 * 60) / 60)
+                      let seconds = Math.floor(total % (60 * 60) % 60)
+                      time = `${this.core.formateTime(hours)}:${this.core.formateTime(minutes)}:${this.core.formateTime(seconds)}`
+                      if (total > 0) this.$set(item, 'end', time)
+                  } else if (item.status === 5) { // 竞赛结束,清除定时器
+                      clearInterval(timer)
+                  }
+              }, 1000)
+              this.timerList.push(timer)
+            }
+          })
+        },
+        // 清除定时器
+        clearTimer() {
+          this.timerList.forEach(n => {
+            clearInterval(n)
+          })
+          this.timerList = []
+        },
+        getData() {
+          // this.loadIns = Loading.service()
+          this.getList()
+          if (!Setting.isDev) {
+            clearInterval(this.redisTimer)
+            this.redisTimer = setInterval(this.getRedis, 1000)
+          }
+        },
+        initData() {
+            this.page = 1
+            this.getData()
+        },
+        // 获取redis缓存
+        getRedis() {
+          this.$post(this.api.getRedisCacheCompetition).then(({ data }) => {
+            data && this.getList()
+          }).catch(res => {})
+        },
+        // 获取省份
+        getProvince() {
+          this.$get(this.api.queryProvince).then(({ list }) => {
+            this.provinces = list
+          }).catch(res => {})
+        },
+        // 获取城市
+        getCity() {
+          const { form } = this
+          form.cityId = ''
+          form.provinceId ?
+            this.$get(this.api.queryCity, {
+              provinceId: form.provinceId
+            }).then(({ list }) => {
+              this.cities = list
+              this.initData()
+            }).catch(res => {}) :
+            this.initData()
+        },
+        changeType(type) {
+          const { form } = this
+          form.competitionScope = 3
+          form.provinceId = ''
+          form.cityId = ''
+          form.sequence = 2
+          form.eventType = type
+          this.initData()
+        },
+        // 筛选范围
+        changeScope(type) {
+            this.form.competitionScope = type
+            this.initData()
+        },
+        // 筛选排序
+        changeSort(type) {
+            this.form.sequence = type
+            this.initData()
+        },
+        toDetail(item) {
+          this.SET_TYPE(this.form.eventType)
+          this.$router.push(`/activity/details?id=${item.id}`);
+        },
+        handleCurrentChange(val) {
+            this.page = val;
+            this.getData();
+        },
+
+        // 个人报名提交
+        peopleSignupSubmit() {
+          this.$post(this.api.addCompetitionRegistration, {
+            competitionId: this.curRow.id,
+            registrationInvitationCode: this.peopleSignupForm.registrationInvitationCode
+          }).then(res => {
+            this.peopleSignupVisible = false
+            this.getData()
+            this.$message.success('报名成功')
+          }).catch(res => {})
+        },
+        // 团队报名提交
+        enterSubmit() {
+          const form = this.enterForm
+          if (!form.teamId) return util.errorMsg('请选择团队')
+          if (!form.invitationCode) return util.errorMsg('请输入团队邀请码')
+          if (this.curItem.setup.isNeedCode && !form.registrationInvitationCode) return util.errorMsg('请输入大赛邀请码')
+          this.$post(this.api.joinCompetitionTeam, form).then(res => {
+            this.enterVisible = false
+            this.getData()
+            util.successMsg('报名成功!')
+          }).catch(res => {})
+        },
+
+        
+        // 创建团队
+        toTeam() {
+          this.teamForm = {
+            competitionId: this.curItem.id,
+            teamName: '',
+            invitationCode: '',
+            registrationInvitationCode: '',
+            whetherSignUp: 1
+          }
+          this.teamVisible = true
+        },
+        // 获取团队列表
+        getTeam() {
+          this.$get(this.api.searchTeam, {
+            teamName: '',
+            competitionId: this.curItem.id
+          }).then(({ teamList }) => {
+            this.teams = teamList
+          }).catch(res => {})
+        },
+        // 团队提交
+        teamSubmit() {
+          const form = this.teamForm
+          if (!form.teamName) return util.errorMsg('请输入团队名称')
+          if (this.teamNameRepeat) return util.errorMsg('团队名称重复,请重新输入')
+          if (form.invitationCode.length !== 6) return util.errorMsg('请输入6位数团队邀请码')
+          if (this.curItem.setup.isNeedCode && !form.registrationInvitationCode) return util.errorMsg('请输入大赛邀请码')
+          this.$post(this.api.addCompetitionTeam, form).then(res => {
+            this.teamVisible = false
+            this.enterVisible = false
+            this.getData()
+            util.successMsg('报名成功!')
+          }).catch(res => {})
+        },
+        stageClick(e) {},
+        // 选择要进入的阶段
+        chooseStage(e, item) {
+          item.curStage = e
+          this.signup(item)
+        },
+        // 判断是否能进赛事
+        getAllow(item) {
+          // 是否允许参加赛事(淘汰赛制)
+          if (item.rule === 1) {
+            this.$post(this.api.allowedParticipateCompetition, {
+              competitionId: item.id,
+              number: item.curStage.number,
+              stageId: item.curStage.stageId,
+              teamId: item.teamId,
+            }).then(res => {
+              this.toSub()
+            }).catch(res => {})
+          } else {
+            this.toSub()
+          }
+        },
+        // 报名
+        signup(item) {
+          const { status, id } = item
+          const { competitionType } = item.setup
+          // 如果没登录,提示去登录
+          if (util.local.get(Setting.tokenKey)) {
+            this.curItem = item
+            if (status == 4) { // 进入比赛
+              // 参加过比赛不让参加
+              if (item.curStage.count) return util.errorMsg('您已经参加过该阶段竞赛!')
+              if (item.isDisable === 1) return util.errorMsg('当前用户已被禁赛,如有疑问,请联系平台管理员。') // 被禁用的用户不能进入大赛
+              // 团队赛,则判断是否为参赛人员
+              if (competitionType) {
+                this.$post(this.api.isParticipant, {
+                  competitionId: id,
+                  stageId: item.curStage.stageId,
+                  teamId: item.teamId,
+                }).then(res => {
+                  this.getAllow(item)
+                }).catch(res => {})
+              } else {
+                this.getAllow(item)
+              }
+            } else if (status == 2) { // 报名
+              // 团队赛报名
+              if (competitionType) {
+                this.getTeam()
+                this.enterForm = {
+                  competitionId: id,
+                  teamId: '',
+                  invitationCode: '',
+                  registrationInvitationCode: ''
+                }
+                this.enterVisible = true
+              } else { // 个人赛报名
+                if (item.setup.isNeedCode) {
+                  this.curRow = item
+                  this.peopleSignupForm.registrationInvitationCode = ''
+                  this.peopleSignupVisible = true
+                } else {
+                  this.$post(this.api.addCompetitionRegistration, {
+                    competitionId: id
+                  }).then(res => {
+                    this.getData()
+                    this.$message.success('报名成功')
+                  }).catch(res => {})
+                }
+              }
+            } else if (status == 1) {
+              // 已报名,点击取消报名
+              this.$confirm('是否要取消报名?', '提示', {
+                type: 'success'
+              }).then(() => {
+                this.$post(`${this.api.cancelRegistration}?competitionId=${item.id}`).then(res => {
+                  this.getData()
+                  this.$message.success('取消报名成功')
+                }).catch(res => {})
+              }).catch(() => {})
+            }
+          } else {
+            this.$confirm('请先登录,是否直接前往登录?', "提示", {
+              type: 'success'
+            }).then(() => {
+              this.SET_SOURCE(item.id)
+              this.$router.push('/login')
+            }).catch(() => {})
+          }
+        },
+        // 进入python系统
+        toPython() {
+          const form = this.curItem.curStage
+          let token = util.local.get(Setting.tokenKey);
+          util.cookies.set('assessmentId', '', -1)
+          util.cookies.set('startTime', '', -1)
+          util.cookies.set('stopTime', '', -1)
+          util.cookies.set('projectId', form.projectId)
+          util.cookies.set('token', token)
+          util.cookies.set('courseId', form.cid)
+          util.cookies.set('curriculumName', escape(form.systemName))
+          util.cookies.set('systemId', form.systemId)
+          util.cookies.set('competitionId', this.curItem.id)
+          util.cookies.set('stageId', form.stageId)
+          util.cookies.set('teamId', this.curItem.teamId)
+          util.cookies.set('stopTime', form.endTime)
+          util.cookies.set('resultsDetails', form.resultsDetails)
+          util.cookies.set('resultAnnouncementTime', form.resultAnnouncementTime)
+          util.cookies.set('fromManager', '', -1)
+          // 8个python子系统都跳这个地址,子系统会通过cookie里的systemId识别展示哪套系统
+          location.href = process.env.NODE_ENV === 'development' ?
+            `http://${location.hostname}:8085/#/` :
+            Setting.isPro ?
+              `https://${location.hostname}/pyTrials` :
+              `${location.origin}/pyTrials`
+        },
+        // 进入子系统
+        toSub() {
+          const form = this.curItem
+          const { systemId, projectId, cid, stageId } = form.curStage
+          const competitionId = form.id
+          const teamId = form.teamId
+          let token = util.local.get(Setting.tokenKey);
+          if (systemId == 11) {
+              // 银行系统
+              location.href = `${Setting.systemPath}/#/index/list?curriculumName=${this.curriculumName}&token=${token}&cid=${cid}&systemId=${systemId}&projectId=${projectId}&competitionId=${competitionId}&stageId=${stageId}&teamId=${teamId}&assessmentId=&classId=&stopTime=&test=true`
+          } else if (systemId == 12) {
+              // 众筹系统
+              window.open(`http://120.78.139.126:8879?systemId=${systemId}&courseId=${cid}&projectId=${projectId}&token=${token}&userId=${this.userId}&classId=1&competitionId=${competitionId}&stageId=${stageId}&teamId=${teamId}`);
+          } else {
+            // python系统
+            this.toPython()
+          }
+        }
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+.search {
+    position: relative;
+    padding: 100px 0 130px;
+    text-align: center;
+    background: url(../../../assets/img/match-bg4.png) (27px 10px)/auto no-repeat,
+                url(../../../assets/img/match-bg5.png) (98% 20px)/auto no-repeat,
+                url(../../../assets/img/match-bg3.png) 0 0/100% 100% no-repeat;
+    h6 {
+        margin-bottom: 25px;
+        font-size: 26px;
+        color: #fff;
+    }
+    .input {
+        position: relative;
+        width: 700px;
+        margin: 0 auto;
+    }
+    img {
+        position: absolute;
+        top: 19px;
+        left: 14px;
+    }
+    input {
+        width: 100%;
+        height: 62px;
+        line-height: 62px;
+        padding: 0 50px;
+        font-size: 18px;
+        color: #333;
+        border: 0;
+        outline: none;
+        border-radius: 4px;
+    }
+}
+.main{
+    background: url(../../../assets/img/match-bg1.png) (0px 95px)/auto auto no-repeat,
+                url(../../../assets/img/match-bg2.png) (98% bottom)/auto auto no-repeat;
+    .center-wrap {
+      width: 1078px;
+    }
+}
+.filter {
+    display: flex;
+    justify-content: space-between;
+    width: 100%;
+    padding: 0 20px;
+    margin-bottom: 20px;
+    background-color: #fff;
+    dl {
+        display: flex;
+        align-items: center;
+        margin: 20px 0;
+        dt {
+            color: #333;
+            font-size: 16px;
+            font-weight: 600;
+            white-space: nowrap;
+        }
+        dd {
+            padding: 5px 15px;
+            color: #6a6a6a;
+            font-size: 16px;
+            white-space: nowrap;
+            cursor: pointer;
+            border-radius: 4px;
+            &.active {
+                color: $main-color;
+                background-color: #e6f0ff;
+            }
+        }
+    }
+}
+.select-wrap {
+  display: inline-flex;
+  align-items: center;
+  margin: 0 10px;
+  .label {
+    margin-right: 10px;
+    white-space: nowrap;
+  }
+  .el-select {
+    width: 130px;
+  }
+}
+.list-inner {
+    display: flex;
+    justify-content: center;
+    align-items:flex-start;
+}
+.nav{
+    width: 156px;
+    text-align: right;
+    overflow: hidden;
+    background-color: #fff;
+    li {
+        padding: 0 24px;
+        font-size: 16px;
+        color: #666;
+        line-height: 48px;
+        border-bottom: 2px solid #f3f6fa;
+        border-right: 2px solid transparent;
+        cursor: pointer;
+        &:before {
+            content: '';
+            display: inline-block;
+            width: 3px;
+            height: 3px;
+            margin-right: 10px;
+            vertical-align: middle;
+            border-radius: 50%;
+            background-color: #666;
+        }
+        &.active {
+            color: $main-color;
+            border-right-color: $main-color;
+            &:before {
+                background-color: $main-color;
+            }
+        }
+    }
+}
+.list-wrap {
+    width: calc(100% - 180px);
+    margin-left: 24px;
+    .list {
+        li {
+            display: flex;
+            justify-content: space-between;
+            padding: 16px;
+            margin-bottom: 12px;
+            transition: all 0.3s;
+            cursor: pointer;
+            border-radius: 6px;
+            background-color: #fff;
+            .right {
+                display: flex;
+                flex-direction: column;
+                justify-content: center;
+                align-items: flex-end;
+                flex: 1;
+
+                .status {
+                    max-width: 120px;
+                    padding: 0 23px;
+                    line-height: 34px;
+                    font-size: 14px;
+                    color: #fff;
+                    white-space: nowrap;
+                    background-color: #52C41A;
+                    border-radius: 4px;
+                    @include ellipsis();
+                    &.wait {
+                        background-color: #FAAD14;
+                    }
+
+                    &.signing {
+                        background-color: $main-color;
+                    }
+
+                    &.signed {
+                        background-color: #52C41A;
+                    }
+                    &.playing {
+                        background-color: #f96d6d;
+                    }
+
+                    &.finish {
+                        background-color: #ccc;
+                    }
+                }
+
+                .btn {
+                    padding: 12px 20px;
+                    color: #fff;
+                    background-color: #cb221c;
+                    border-radius: 4px;
+                    cursor: pointer;
+
+                    &:hover {
+                        opacity: .9;
+                    }
+
+                    &.disabled {
+                        cursor: not-allowed;
+                        background-color: #969696;
+                    }
+                }
+
+                .end-text {
+                    margin-top: 10px;
+                    color: rgba(0, 0, 0, .65);
+                    font-size: 12px;
+                    white-space: nowrap;
+
+                    em {
+                        font-style: normal;
+                        color: #f00;
+                    }
+                }
+            }
+
+            &:hover {
+                .left {
+                    .info {
+                        .title {
+                            color: $main-color;
+                        }
+                    }
+                }
+            }
+        }
+        .left {
+            position: relative;
+            display: inline-flex;
+            .stage-label {
+              position: absolute;
+              top: -16px;
+              left: -16px;
+              border: 0;
+              background-color: rgb(101, 227, 181);
+              &.playing {
+                background-color: rgb(251, 174, 41);
+              }
+            }
+            .cover {
+                img {
+                    width: 275px;
+                    height: 175px;
+                    border-radius: 6px;
+                }
+            }
+
+            .info {
+                margin-left: 16px;
+                .title {
+                    margin-bottom: 10px;
+                    font-size: 20px;
+                    font-weight: 500;
+                    color: #0B1D30;
+                    line-height: 1;
+                }
+                .metas {
+                    font-size: 14px;
+                    color: #666;
+                    div {
+                        display: flex;
+                        align-items: center;
+                        margin-bottom: 5px;
+
+                        &.flex-top {
+                            align-items: flex-start;
+                        }
+                    }
+
+                    .label, .val {
+                        font-size: 14px;
+                        color: #666;
+                        white-space: nowrap;
+                    }
+
+                    .val {
+                        max-width: 350px;
+                        text-overflow: ellipsis;
+                        overflow: hidden;
+                    }
+
+                    .a-line {
+                        display: block;
+                    }
+                }
+
+                .desc {
+                    font-size: 14px;
+                }
+            }
+        }
+    }
+}
+/deep/.dia-form {
+  .w-100 {
+    width: 100%;
+  }
+  .tips {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/activity/manage/add/index.vue b/src/pages/activity/manage/add/index.vue
new file mode 100644
index 0000000..e0dccd4
--- /dev/null
+++ b/src/pages/activity/manage/add/index.vue
@@ -0,0 +1,512 @@
+<template>
+    <div>
+        <el-card v-if="!id" shadow="hover" class="m-b-20">
+            <div class="flex-between">
+                <el-page-header @back="back" :content="'创建项目'"></el-page-header>
+            </div>
+        </el-card>
+        <div class="page">
+          <div class="page-content">
+            <el-form label-width="170px" label-suffix=":" size="small">
+                <el-form-item label="项目封面(选填)">
+                    <el-upload
+                        class="avatar-uploader"
+                        accept=".jpg,.png,.jpeg,.gif"
+                        :on-success="uploadSuccess"
+                        :limit="1"
+                        :action="this.api.fileupload"
+                        :headers="headers"
+                        name="file"
+                    >
+                        <img v-if="form.coverUrl" :src="form.coverUrl" class="avatar">
+                        <div class="uploader-default" v-else>
+                            <i class="el-icon-plus"></i>
+                            <p>上传封面</p>
+                        </div>
+                        <div slot="tip" class="el-upload__tip">
+                            <p>展示宽度为220,高度140,JPG/PNG/GIF,3MB以内</p>
+                        </div>
+                    </el-upload>
+                </el-form-item>
+                <el-form-item label="项目封面长图(选填)">
+                    <el-upload
+                        class="avatar-uploader avatar-uploader-lg"
+                        accept=".jpg,.png,.jpeg,.gif"
+                        :on-success="uploadLgSuccess"
+                        :limit="1"
+                        :action="this.api.fileupload"
+                        :headers="headers"
+                        name="file"
+                    >
+                        <img v-if="form.carouselUrl" :src="form.carouselUrl" class="avatar-lg">
+                        <div class="uploader-default" v-else>
+                            <i class="el-icon-plus"></i>
+                            <p>上传封面</p>
+                        </div>
+                        <div slot="tip" class="el-upload__tip">
+                            <p>展示宽度为1920,高度300,JPG/PNG/GIF,3MB以内</p>
+                        </div>
+                    </el-upload>
+                </el-form-item>
+                <el-form-item class="req" label="项目名称">
+                    <div class="d-inline-block">
+                        <el-input placeholder="请输入项目名称" v-model="form.projectName" clearable></el-input>
+                    </div>
+                </el-form-item>
+                <el-form-item class="req" label="发起方">
+                    <div class="inline-input">
+                        <div class="input-wrap" v-for="(item,index) in sponsorList" :key="index">
+                            <el-input placeholder="发起方名称" v-model="sponsorList[index]"></el-input>
+                            <i v-if="sponsorList.length > 1" class="remove" @click="delSponsor(index)"></i>
+                            <button v-if="index == 0" class="add-btn" type="button" @click="addSponsor">
+                                <i class="el-icon-plus"></i>
+                                <span>添加</span>
+                            </button>
+                        </div>
+                    </div>
+                </el-form-item>
+                <el-form-item class="req" label="报名时间">
+                    <el-date-picker v-model="signupTime" value-format="yyyy-MM-dd HH:mm:ss" type="datetimerange"
+                                    range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
+                                    :picker-options="pickerOptions"></el-date-picker>
+                </el-form-item>
+                <el-form-item class="req" label="项目时间">
+                    <el-date-picker v-model="playTime" value-format="yyyy-MM-dd HH:mm:ss" type="datetimerange"
+                                    range-separator="-" start-placeholder="开始日期" end-placeholder="结束日期"
+                                    :picker-options="pickerOptions"></el-date-picker>
+                </el-form-item>
+                <el-form-item class="req" label="发布范围">
+                  <div>
+                    <el-radio v-model="scope" :label="0" disabled>本校内</el-radio>
+                  </div>
+                </el-form-item>
+                <el-form-item class="req" label="报名人数上限">
+                    <div class="input-center">
+                        <el-input placeholder="请输入人数" v-model.number="form.maximumNumber" type="number"></el-input> 人
+                    </div>
+                </el-form-item>
+                <el-form-item class="req" label="报名邀请码">
+                  <div class="input-center" style="width: 550px;">
+                    <el-radio v-model="form.isNeedCode" :label="0">不需要</el-radio>
+                    <el-radio v-model="form.isNeedCode" :label="1">需要</el-radio>
+                    <el-input style="width: 250px" placeholder="请输入4位邀请码或点击随机生成" v-model="form.invitationCode" :disabled="form.isNeedCode === 0"></el-input>
+                    <el-button v-if="form.isNeedCode === 1" @click="randomInv">随机</el-button>
+                  </div>
+                </el-form-item>
+                <el-form-item class="req" label="项目详情">
+                    <quill ref="quill" :border="true" v-model="form.projectDescribe" :height="400" />
+                </el-form-item>
+                <el-form-item label="附件">
+                  <el-upload
+                      :before-upload="beforeUpload"
+                      :on-success="uploadAnnexSuccess"
+                      :limit="5"
+                      :action="this.api.fileupload"
+                      :headers="headers"
+                      :file-list="fileList"
+                      name="file"
+                  >
+                    <el-button size="small" type="primary">点击上传</el-button>
+                    <div slot="tip" class="el-upload__tip">
+                      <p>支持扩展名:.rar .zip .doc .docx .pdf .jpg...</p>
+                    </div>
+                  </el-upload>
+                </el-form-item>
+            </el-form>
+
+            <div class="btns">
+              <el-button @click="save(0)">保存</el-button>
+              <el-button type="primary" @click="save(1)">发布</el-button>
+              <el-button type="danger" @click="preview" v-auth="'/activity/list:管理:大赛详情:预览'">预览</el-button>
+              <el-button @click="back">取消</el-button>
+            </div>
+          </div>
+        </div>
+    </div>
+</template>
+
+<script>
+import util from "@/libs/util";
+import quill from "@/components/quill";
+import Setting from "@/setting";
+export default {
+    data() {
+        return {
+            id: this.$route.query.id || '',
+            headers: {
+                token: util.local.get(Setting.tokenKey)
+            },
+            scope: 0,
+            form: {
+              id: this.$route.query.id || '',
+              founder: 2,
+              isOpen: 0, // 职站是否开启(0开启 1未开启 默认0)
+              maximumNumber: '',
+              carouselUrl: '',
+              coverUrl: '',
+              activityFileList: [], // 赛事附件
+              initiator: '',
+              isNeedCode: 0,
+              maximumNumber: 0,
+              signUpStartTime: '',
+              signUpEndTime: '',
+              playStartTime: '',
+              playEndTime: '',
+              projectDescribe: '',
+              projectName: '',
+              publishStatus: 0,
+            },
+            pickerOptions: {
+                disabledDate: time => {
+                    return this.$route.query.id ? false : time.getTime() < new Date().getTime() - 86400000;
+                }
+            },
+            fileName: '',
+            signupTime: [],
+            playTime: [],
+            sponsorList: [""],
+            fileList: [],
+            submiting: false,
+            updateTime: 0,
+        };
+    },
+    components: {
+      quill,
+    },
+    watch: {
+        // 监听信息是否有更改,有的话页面离开的时候要询问是否要保存
+        form: {
+          handler(){
+            this.updateTime++
+          },
+          deep:true
+        },
+        signupTime: function(val) {
+          const { form } = this
+          if (val) {
+              form.signUpStartTime = val[0];
+              form.signUpEndTime = val[1];
+          } else {
+              form.signUpStartTime = ''
+              form.signUpEndTime = ''
+          }
+        },
+        playTime: function(val) {
+          const { form } = this
+          if (val) {
+              form.playStartTime = val[0]
+              form.playEndTime = val[1]
+          } else {
+              form.playStartTime = ''
+              form.playEndTime = ''
+          }
+        }
+    },
+    mounted() {
+      this.getData()
+    },
+    methods: {
+      getData() {
+        const { id } = this.form
+        id && this.$post(`${this.api.findByIdActivity}?id=${id}`).then(({ data }) => {
+          if (data.signUpStartTime) this.signupTime = [data.signUpStartTime, data.signUpEndTime]
+          if (data.playStartTime) this.playTime = [data.playStartTime, data.playEndTime]
+          this.sponsorList = data.initiator.split(",")
+          // 附件
+          const fileList = data.activityFileList
+          if (fileList) {
+            const files = []
+            fileList.map(e => {
+              files.push({
+                name: e.fileName,
+                url: e.filePath
+              })
+            })
+            this.fileList = files
+          } else {
+            data.activityFileList = []
+          }
+
+          this.form = data
+          this.$nextTick(() => {
+            this.updateTime = 0
+          })
+        }).catch(err => {})
+      },
+      uploadSuccess(res) {
+        this.form.coverUrl = res.data.filesResult.fileUrl
+      },
+      uploadLgSuccess(res) {
+        this.form.carouselUrl = res.data.filesResult.fileUrl
+      },
+      // 附件上传成功
+      uploadAnnexSuccess(res) {
+        const file = res.data.filesResult
+        const { id } = this.form
+        const data = {
+          activityId: id || '',
+          fileName: this.fileName,
+          filePath: file.fileUrl || file.fileId
+        }
+        this.form.activityFileList.push(data)
+      },
+      // 附件上传前
+      beforeUpload(file) {
+        const isLt2M = file.size / 1024 / 1024 < 10
+        if (!isLt2M) util.warningMsg('请上传小于10MB的附件!')
+        if (isLt2M) {
+          this.fileName = file.name
+          return true
+        } else {
+          return false
+        }
+      },
+      handleAnnexRemove(file, fileList) {
+        
+      },
+      // 随机邀请码
+      randomInv() {
+        let result = ''
+        for (let i = 0; i < 4; i++) {
+          result += Math.floor(Math.random() * 10);
+        }
+        this.form.completeCompetitionSetup.invitationCode = result
+      },
+      // 提交
+      save(status) {
+          const { form } = this
+          form.initiator = this.sponsorList.filter(d => d).join();
+          if (!form.projectName) return util.warningMsg("请填写项目名称");
+          // 发布需要校验
+          if (status) {
+            if (!form.initiator) return util.warningMsg("请填写发起方");
+            if (!form.signUpStartTime) return util.warningMsg("请选择报名时间");
+            let now = new Date().getTime();
+            let signUpStartTime = new Date(form.signUpStartTime).getTime();
+            let signUpEndTime = new Date(form.signUpEndTime).getTime();
+            let playStartTime = new Date(form.playStartTime).getTime();
+            // if (signUpStartTime && now > signUpStartTime) return util.warningMsg("报名时间不能早于当前时间");
+            if (!form.playStartTime) return util.warningMsg("请选择项目时间");
+            if (playStartTime && signUpEndTime && playStartTime < signUpEndTime) return util.warningMsg("项目时间不能早于报名结束时间");
+            if (form.isNeedCode && (!form.invitationCode || form.invitationCode.length !== 4)) return util.warningMsg('请填写四位数邀请码')
+            if (!form.projectDescribe) return util.warningMsg("请填写项目详情");
+          }
+          form.publishStatus = status
+          form.id = this.$route.query.id
+          if (form.id) {
+              this.$post(this.api.updateActivity, form).then(res => {
+                  this.updateTime = 0
+                  this.$router.back()
+                  util.successMsg("修改成功");
+              }).catch(err => {});
+          } else {
+              this.$post(this.api.saveActivity, form).then(res => {
+                  this.updateTime = 0
+                  this.$router.back()
+                  util.successMsg("创建成功");
+              }).catch(err => {});
+          }
+      },
+      // 预览
+      preview() {
+        util.local.set('activity', this.form)
+        window.open(this.$router.resolve('/matchPreview').href)
+      },
+      addSponsor() {
+          this.sponsorList.push("");
+      },
+      delSponsor(index) {
+          this.sponsorList.splice(index, 1);
+      },
+      back() {
+        // 更改了信息才需要提示
+        const { updateTime } = this.$refs['step' + this.step]
+        console.log("🚀 ~ file: index.vue:142 ~ back ~ updateTime", updateTime)
+        if (this.step < 4 && updateTime) {
+          this.$confirm(`编辑的内容未保存,是否保存?`, '提示', {
+            type: 'warning'
+          }).then(() => {
+            this.save(0)
+          }).catch(() => {
+            this.backPage()
+          })
+        } else {
+          this.backPage()
+        }
+      },
+      backPage() {
+        this.$router.push(`/activity?page=${this.$store.state.activity.page}`)
+      }
+    }
+};
+</script>
+
+<style scoped lang="scss">
+$upload-width: 220px;
+$upload-height: 140px;
+$upload-lg-height: 150px;
+/deep/ .avatar-uploader {
+    .el-upload {
+        position: relative;
+        width: $upload-width;
+        height: $upload-height;
+        border: 1px dashed #d9d9d9;
+        border-radius: 6px;
+        cursor: pointer;
+        overflow: hidden;
+
+        &:hover {
+            border-color: #cb221c;
+        }
+
+        .uploader-default {
+            display: flex;
+            height: $upload-height;
+            flex-direction: column;
+            justify-content: center;
+            text-align: center;
+            background: rgba(0, 0, 0, 0.04);
+
+            i {
+                font-size: 20px;
+                font-weight: bold;
+                color: #8c939d;
+            }
+
+            p {
+                margin-top: 10px;
+                font-size: 14px;
+                color: rgba(0, 0, 0, 0.65);
+                line-height: 1;
+            }
+        }
+    }
+
+    &.avatar-uploader-lg {
+        .el-upload {
+            width: 100%;
+            max-width: 960px;
+            height: $upload-lg-height;
+
+            .uploader-default {
+                height: $upload-lg-height;
+            }
+        }
+    }
+
+    .avatar {
+        display: block;
+        width: $upload-width;
+        height: $upload-height;
+    }
+
+    .avatar-lg {
+        display: block;
+        width: 100%;
+        height: $upload-lg-height;
+    }
+
+    .el-upload__tip {
+        margin-top: 0;
+
+        p {
+            font-size: 14px;
+            color: rgba(0, 0, 0, 0.45);
+            line-height: 1;
+
+            &:first-child {
+                margin-bottom: 5px;
+            }
+        }
+    }
+}
+
+/deep/ .d-inline-block {
+    width: 216px;
+
+    .el-select, .el-input {
+        width: 100%;
+    }
+}
+.inline-input {
+    .input-wrap {
+        display: flex;
+        align-items: center;
+        margin-bottom: 10px;
+
+        .el-input {
+            display: inline-block;
+            width: 216px;
+            margin-right: 8px;
+        }
+
+        .remove {
+            width: 16px;
+            height: 16px;
+            background: url("../../../../assets/img/close.png") 0 0/cover no-repeat;
+            cursor: pointer;
+        }
+    }
+
+    .add-btn {
+        margin-left: 32px;
+    }
+}
+
+.add-btn {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 216px;
+    line-height: 32px;
+    font-size: 14px;
+    color: rgba(0, 0, 0, 0.65);
+    background-color: transparent;
+    border: 1px dashed rgba(0, 0, 0, 0.15);
+    border-radius: 4px;
+    cursor: pointer;
+
+    i {
+        margin-right: 8px;
+        font-size: 14px;
+        font-weight: bold;
+    }
+}
+.range-check {
+  display: inline-block;
+  margin: 0 0 10px 10px;
+}
+/deep/.range-cas {
+  .el-tag {
+    display: none;
+  }
+}
+.input-center {
+  display: flex;
+  align-items: center;
+  width: 216px;
+  white-space: nowrap;
+  .el-input {
+    margin-right: 5px;
+  }
+}
+.el-steps {
+  justify-content: center;
+}
+/deep/.req {
+  .el-form-item__label {
+    &:before {
+      content: '*';
+      margin-right: 5px;
+      font-size: 18px;
+      vertical-align: middle;
+      color: #f00;
+    }
+  }
+}
+.btns {
+  display: flex;
+  justify-content: center;
+  text-align: center;
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/activity/manage/list/index.vue b/src/pages/activity/manage/list/index.vue
new file mode 100644
index 0000000..8d46d30
--- /dev/null
+++ b/src/pages/activity/manage/list/index.vue
@@ -0,0 +1,367 @@
+<template>
+    <div class="page">
+        <h6 class="p-title">筛选</h6>
+        <div class="tool mul">
+            <ul class="filter">
+                <li>
+                    <label>创建时间:</label>
+                    <div class="single-choice">
+                        <dl>
+                            <dd>
+                                <el-radio-group v-model="form.month" @change="changeType">
+                                    <el-radio v-for="(item,index) in dateList" :key="index" :label="item.id" border>{{ item.name }}</el-radio>
+                                </el-radio-group>
+                            </dd>
+                        </dl>
+                    </div>
+                </li>
+                <li>
+                    <label>创建区间:</label>
+                    <el-date-picker v-model="date" align="right" unlink-panels type="daterange" start-placeholder="开始日期" end-placeholder="结束日期" format="yyyy-MM-dd" value-format="yyyy-MM-dd" clearable></el-date-picker>
+                </li>
+                <li>
+                    <label>搜索:</label>
+                    <el-input placeholder="请输入项目名称/创建人" suffix-icon="el-icon-search" v-model="keyword" clearable></el-input>
+                </li>
+            </ul>
+        </div>
+        <div class="tool mul">
+            <ul class="filter">
+                <li>
+                    <label>状态:</label>
+                    <dl>
+                        <dd>
+                            <el-radio-group v-model="form.publishStatus" @change="changeType">
+                                <el-radio v-for="(item,index) in statuses" :key="index" :label="item.id" border>{{ item.name }}</el-radio>
+                            </el-radio-group>
+                        </dd>
+                    </dl>
+                </li>
+            </ul>
+            <div>
+                <el-button type="primary" round @click="add" v-auth>创建项目</el-button>
+                <el-button type="primary" round @click="delAllSelection" v-auth>批量删除</el-button>
+            </div>
+        </div>
+
+        <el-table ref="table" :data="activityData" class="table" stripe header-align="center" @selection-change="handleSelectionChange" row-key="id">
+            <el-table-column type="selection" width="55" align="center" :reserve-selection="true"></el-table-column>
+            <el-table-column type="index" width="60" label="序号" align="center">
+                <template slot-scope="scope">
+                    {{ scope.$index + (page - 1) * pageSize + 1 }}
+                </template>
+            </el-table-column>
+            <el-table-column prop="projectName" label="项目名称" align="center"></el-table-column>
+            <el-table-column prop="applicantNum" label="报名人数" align="center" width="150"></el-table-column>
+            <el-table-column prop="status" label="状态" align="center" width="90">
+              <template slot-scope="scope">
+                {{ scope.row.publishStatus ? '已发布' : '未发布' }}
+              </template>
+            </el-table-column>
+            <el-table-column prop="time" label="项目时间" align="center" width="300">
+                <template slot-scope="scope">
+                    {{ scope.row.playStartTime }} ~ {{ scope.row.playEndTime }}
+                </template>
+            </el-table-column>
+            <el-table-column prop="createTime" label="创建时间" align="center" width="160"></el-table-column>
+            <el-table-column prop="founderName" label="创建人" align="center" width="130">
+              <template slot-scope="scope">
+                {{ scope.row.founderName || '学校超管' }}
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" width="170">
+                <template slot-scope="scope">
+                    <el-button v-auth type="text" @click="manage(scope.row)">管理</el-button>
+                    <el-button v-auth type="text" @click="delData(scope.row)">删除</el-button>
+                    <!-- 列表展示中台的禁启用依据zt_open展示,职站的禁启用依据is_open展示 -->
+                    <!-- 中台禁用了这个学校发布的学校这边还能看到,但是学校这边不能启用。 -->
+                    <el-switch
+                        v-if="scope.row.publishStatus"
+                        v-auth="'禁用'"
+                        v-model="scope.row.isOpen"
+                        :active-value="0"
+                        :inactive-value="1"
+                        style="margin: 0 10px 0 5px"
+                        :active-text="scope.row.isOpen ? '关' : '开'"
+                        @change="switchOff($event,scope.row,scope.$index)"
+                    ></el-switch>
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="pagination">
+            <el-pagination background layout="total, prev, pager, next" :total="total" @current-change="handleCurrentChange" :current-page="page">
+            </el-pagination>
+        </div>
+    </div>
+</template>
+
+<script>
+import util from "@/libs/util";
+import Setting from "@/setting";
+import { mapMutations } from "vuex";
+import { Loading } from 'element-ui'
+export default {
+    data() {
+        return {
+            timer: null,
+            keyword: "",
+            activityData: [],
+            statuses: [
+              {
+                id: '',
+                name: '不限'
+              },
+              {
+                id: 0,
+                name: '待发布'
+              },
+              {
+                id: 1,
+                name: '已发布'
+              }
+            ],
+            form: {
+                month: "",
+                publishStatus: "",
+                startTime: "",
+                endTime: "",
+            },
+            multipleSelection: [],
+            dateList: [
+                {
+                    id: "",
+                    name: "不限"
+                },
+                {
+                    id: 1,
+                    name: "近一个月"
+                },
+                {
+                    id: 3,
+                    name: "近三个月"
+                },
+                {
+                    id: 6,
+                    name: "近六个月"
+                }
+            ],
+            date: [],
+            page: +this.$route.query.page || 1,
+            pageSize: 10,
+            total: 0,
+            transferPublishStatus: [ "未发布", "已发布"],
+            modifyVisible: false,
+            curRow: {
+              playingStages: []
+            },
+            timer: null,
+            redisTimer: null,
+            pickerOptions: {
+              shortcuts: [{
+                text: '此刻',
+                onClick(picker) {
+                  picker.$emit('pick', new Date(Date.now() + 5000))
+                }
+              }]
+            }
+        };
+    },
+    watch: {
+        "form.month": function(val) {
+            if (val) {
+                let unit = 24 * 60 * 60 * 1000;
+                this.date = [util.formatDate("yyyy-MM-dd", new Date(new Date().getTime() - unit * 30 * val)), util.formatDate("yyyy-MM-dd", new Date(new Date().getTime() + unit))];
+            } else {
+                this.date = [];
+            }
+        },
+        date: function(val) {
+            if (val) {
+                this.form.startTime = val[0];
+                this.form.endTime = val[1];
+            } else {
+                this.form.startTime = "";
+                this.form.endTime = "";
+            }
+            this.initData();
+        },
+        keyword: function(val) {
+            clearTimeout(this.searchTimer);
+            this.searchTimer = setTimeout(() => {
+                this.initData();
+            }, 500);
+        }
+    },
+    mounted() {
+        this.getData()
+        this.$once('hook:beforeDestroy', function() {
+          clearInterval(this.timer)
+          clearInterval(this.redisTimer)
+        })
+    },
+    methods: {
+        // ...mapMutations('activity', [
+        //   'setPage'
+        // ]),
+        getList() {
+          // const load = Loading.service()
+          const { form } = this
+          this.$post(this.api.myActivities, {
+            pageNum: this.page,
+            pageSize: this.pageSize,
+            endTime: form.endTime || null,
+            keyWords: this.keyword || null,
+            platformSource: 2, // 当前所属平台(0:中台,1:职站教师 2职站学生)
+            startTime: form.startTime || null,
+            publishStatus: form.publishStatus === '' ? null : form.publishStatus
+          }).then(({ data }) => {
+            // load.close()
+            if (data) {
+              const list = data.records
+              // 定时处理是否要显示修改结束时间按钮
+              this.timer = setInterval(() => {
+                const now = new Date()
+                list.map(e => {
+                  if (!e.playingStages) {
+                    this.$set(e, 'playingStages', [])
+                  } else {
+                    e.playingStages = []
+                  }
+                  // 如果当前时间在项目开始结束时间之间
+                  if (now >= new Date(e.playStartTime) && now <= new Date(e.playEndTime)) {
+                    // 遍历赛事阶段
+                    if (e.competitionStageList) {
+                      for (const n of e.competitionStageList) {
+                        // 判断是否有开始了的阶段
+                        if (now >= new Date(n.startTime) && now <= new Date(n.endTime)) {
+                          e.playingStages.push(n)
+                        }
+                      }
+                    }
+                  }
+                })
+              }, 1000)
+              list.map(e => {
+                if (e.ztOpen) e.isOpen = 1
+              })
+              this.activityData = list
+              this.total = data.total
+              this.$refs.table.clearSelection()
+              if (!this.activityData.length && this.total) {
+                this.page--
+                this.getData()
+              }
+            }
+          }).catch(res => {
+            // load.close()
+          })
+        },
+        getData() {
+          this.getList()
+        },
+        initData() {
+            this.page = 1;
+            this.getData();
+        },
+        // 获取redis缓存
+        getRedis() {
+          this.$post(this.api.getRedisCacheActivity).then(({ data }) => {
+            data && this.getList()
+          }).catch(res => {})
+        },
+        getData() {
+          this.getList()
+          if (!Setting.isDev) {
+            clearInterval(this.redisTimer)
+            this.redisTimer = setInterval(this.getRedis, 1000)
+          }
+        },
+        add() {
+            this.$router.push("add");
+        },
+        manage(row) {
+            this.$router.push(`manage?id=${row.id}&name=${row.projectName}`)
+        },
+
+        changeType() {
+            this.$refs.table.clearSelection();
+            this.initData();
+        },
+        delData(row) {
+            this.$confirm("此删除操作不可逆,是否确认删除选中项?", "提示", {
+                type: "warning"
+            })
+                .then(() => {
+                    this.$post(`${this.api.batchDeletionActivity}?ids=${row.id}`).then(res => {
+                        util.successMsg("删除成功");
+                        this.getData();
+                    }).catch(res => {
+                    });
+                })
+                .catch(() => {
+                });
+        },
+        handleSelectionChange(val) {
+            this.multipleSelection = val;
+        },
+        // 批量删除
+        delAllSelection() {
+            if (this.multipleSelection.length) {
+                this.$confirm("确定要删除吗?", "提示", {
+                    type: "warning"
+                }).then(() => {
+                    let ids = this.multipleSelection.map(i => 'ids=' + i.id);
+                    this.$post(`${this.api.batchDeletionActivity}?${ids.join('&')}`).then(res => {
+                        this.getData();
+                        this.$message.success("删除成功");
+                        this.$refs.table.clearSelection()
+                    }).catch(err => {
+                    });
+                }).catch(() => {
+                });
+            } else {
+                this.$message.warning("请先选择赛事 !");
+            }
+        },
+        handleCurrentChange(val) {
+            this.page = val;
+            this.$router.push(`list?page=${val}`)
+            this.getData()
+            // this.setPage(val)
+        },
+        transferTime(date, type) {
+            if (date == "0000-00-00 00:00:00") return "---";
+            return date;
+        },
+        switchOff(val, row) {
+          this.$post(this.api.disabledEventsActivity, {
+            activityId: row.id,
+            isOpen: val,
+            type: 2 // 禁用平台来源(0中台,1职站)
+          }).then(res => {}).catch(err => {})
+        },
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+/deep/ .tool {
+    .filter {
+        .el-input {
+            min-width: 190px;
+        }
+    }
+}
+
+@media(max-width: 1640px) {
+    .page .page-content .tool .filter {
+        flex-wrap: wrap;
+        margin-bottom: -15px;
+
+        li {
+            min-width: 34%;
+            margin-bottom: 15px;
+        }
+    }
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/activity/manage/manage/index.vue b/src/pages/activity/manage/manage/index.vue
new file mode 100644
index 0000000..b6da536
--- /dev/null
+++ b/src/pages/activity/manage/manage/index.vue
@@ -0,0 +1,129 @@
+<template>
+    <!--  赛事管理  -->
+    <div>
+        <el-card shadow="hover" class="m-b-20">
+            <div class="flex-between">
+                <el-page-header @back="back" :content="name + '/管理'"></el-page-header>
+            </div>
+        </el-card>
+        <div class="page" style="margin-bottom: 24px">
+            <div class="tabs">
+                <a class="item" v-for="(item,index) in tabs" :key="index" :class="{active: index == active}" @click="tabChange(index)">{{ item }}</a>
+            </div>
+            <MatchDetail v-if="active == 'tab1'" ref="detail" />
+            <MatchProgress v-else-if="active == 'tab2'" />
+            <notice v-else-if="active == 'tab3'" />
+            <MatchSignup v-else-if="active == 'tab4'" />
+        </div>
+    </div>
+
+</template>
+
+<script>
+import Setting from "@/setting";
+import MatchDetail from "../add";
+import MatchProgress from "./matchProgress";
+import notice from "./notice";
+import MatchSignup from "./matchSignup";
+import { mapState } from "vuex";
+export default {
+    name: "matchManage",
+    data() {
+        return {
+            name: this.$route.query.name,
+            active: this.$route.query.tab || "tab1",
+            tabs: {
+                tab1: "项目详情",
+                tab2: "项目进展",
+                tab3: "公告通知",
+                tab4: "报名人员"
+            },
+        };
+    },
+    components: {
+        MatchDetail,
+        MatchProgress,
+        notice,
+        MatchSignup
+    },
+    // computed: {
+    //   ...mapState("user", [
+    //     'page'
+    //   ]),
+    //   ...mapState('auth', [
+    //     'btns'
+    //   ])
+    // },
+    mounted() {
+      // Setting.dynamicRoute && this.initTabs()
+    },
+    methods: {
+        initTabs() {
+          // const { btns } = this
+          // const tab1 = btns.includes('/activity/list:管理:大赛详情')
+          // const tab3 = btns.includes('/activity/list:管理:竞赛进展')
+          // const tab4 = btns.includes('/activity/list:管理:公告通知')
+          // const tab5 = btns.includes('/activity/list:管理:报名人员')
+
+          // tab1 || this.$delete(this.tabs, 'tab1')
+          // tab3 || this.$delete(this.tabs, 'tab3')
+          // tab4 || this.$delete(this.tabs, 'tab4')
+          // tab5 || this.$delete(this.tabs, 'tab5')
+          // const type = this.$route.query.tab
+          // const keys = Object.keys(this.tabs)
+          // this.active = keys.includes(type) ? type : keys[0]
+        },
+        // 移除成绩管理
+        hideArch() {
+          this.$delete(this.tabs, 'tab2')
+        },
+        back() {
+          this.handleSave(0) && this.backPage()
+        },
+        // 判断是否需要confirm
+        handleSave(i) {
+          // 如果是赛事详情,则要判断是否已经保存,未保存则提示是否保存
+          if (this.active === 'tab1') {
+            const detail = this.$refs.detail
+            if (detail.step < 4 && detail.$refs['step' + detail.step].updateTime) {
+              this.$confirm(`编辑的内容未保存,是否保存并且发布?`, '提示', {
+                type: 'warning'
+              }).then(() => {
+                detail.save(1, 1)
+                this.backOrTab(i)
+              }).catch(() => {
+                this.backOrTab(i)
+              })
+            } else {
+              this.backOrTab(i)
+            }
+            return false
+          } else {
+            return true
+          }
+        },
+        // 返回列表
+        backPage(){
+          console.log(444, this.$store.state)
+          this.$router.push(`/activity?page=${this.$store.state.activity.page}`)
+        },
+        // tab切换
+        tabSwitch(i) {
+          this.active = i
+          this.$router.push(`/activity/manage?id=${this.$route.query.id}&tab=${i}&name=${this.name}`)
+        },
+        // 判断返回还是tab
+        backOrTab(i) {
+          i ? this.tabSwitch(i) : this.backPage()
+        },
+        // tab回调
+        tabChange(i) {
+          this.handleSave(i) && this.tabSwitch(i)
+        }
+    }
+};
+</script>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/pages/activity/manage/manage/matchProgress.vue b/src/pages/activity/manage/manage/matchProgress.vue
new file mode 100644
index 0000000..605b7d1
--- /dev/null
+++ b/src/pages/activity/manage/manage/matchProgress.vue
@@ -0,0 +1,240 @@
+<template>
+    <!-- 竞赛进展 -->
+    <div class="page-content" style="padding: 24px">
+        <el-table ref="table" :data="listData" class="table" stripe header-align="center" @selection-change="handleSelectionChange" row-key="id">
+            <el-table-column type="index" width="60" label="序号" align="center">
+                <template slot-scope="scope">
+                    {{ scope.$index + (pageNo - 1) * pageSize + 1 }}
+                </template>
+            </el-table-column>
+            <el-table-column prop="name" label="标题">
+                <template slot-scope="scope">
+                    <el-input placeholder="请输入标题" :disabled="!scope.row.operate" v-model="scope.row.title"></el-input>
+                </template>
+            </el-table-column>
+            <el-table-column prop="name" label="详情描述">
+                <template slot-scope="scope">
+                    <el-input placeholder="请输入详情描述" :disabled="!scope.row.operate"  type="textarea" v-model="scope.row.description"></el-input>
+                </template>
+            </el-table-column>
+            <el-table-column prop="name" label="状态" width="150">
+                <template slot-scope="scope">
+                    <el-select v-model="scope.row.status" :disabled="!scope.row.operate" clearable placeholder="请选择状态">
+                        <el-option v-for="(item,index) in statusList" :key="index" :label="item.name" :value="item.value"></el-option>
+                    </el-select>
+                </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" width="170">
+                <template slot-scope="scope">
+                    <el-button v-if="!scope.row.operate" type="text" @click="operateIt(scope.row)" v-auth="'/activity:管理:竞赛进展:编辑'">编辑</el-button>
+                    <el-button v-else type="text" @click="saveData(scope.row)">保存</el-button>
+                    <el-button type="text" @click="handleDelete(scope.row, scope.$index)" v-auth="'/activity/list:管理:竞赛进展:删除'">删除</el-button>
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="plus" @click="addData" v-auth="'/activity/list:管理:竞赛进展:新增'">
+            <i class="el-icon-circle-plus-outline"></i>
+        </div>
+    </div>
+</template>
+
+<script>
+import util from "@/libs/util";
+export default {
+    name: "matchProgress",
+    data() {
+        return {
+            save: false,
+            id: this.$route.query.id,
+            statusList: [
+                {
+                    value: 0,
+                    name: "未完成"
+                },
+                {
+                    value: 1,
+                    name: "进行中"
+                },
+                {
+                    value: 2,
+                    name: "已完成"
+                }
+            ],
+            listData: [],
+            multipleSelection: [],
+            pageNo: 1,
+            pageSize: 10,
+            totals: 0,
+            touchTime:0,
+            timeOut: {}
+        };
+    },
+    mounted() {
+        this.getData();
+    },
+    methods: {
+        operateIt(row) {
+            row.operate = true
+        },
+        getData() {
+            this.$get(this.api.getCompetitionProgress, {
+              competitionId: this.id
+            }).then(res => {
+                this.listData = res.competitionProgressList;
+                for(let index=0; index<this.listData.length; index++) {
+                    // 未点编辑时不可输入
+                    this.$set(this.listData, index, { operate: false, ...this.listData[index]})
+                }
+            }).catch(res => {
+            });
+        },
+        saveData(row) {
+            // 有点编辑的时候就要等待确认
+            let data = row;
+            if (data.title.length) {
+                if (row.id) {
+                    this.$put(this.api.editCompetitionProgress, data).then(res => {
+                        this.touchTime = this.touchTime-1
+                        util.successMsg("修改成功");
+                        this.getData();
+                    }).catch(res => {
+                    });
+                } else {
+                    this.$post(this.api.addCompetitionProgress, data).then(res => {
+                        this.touchTime = this.touchTime-1
+                        util.successMsg("创建成功");
+                        this.getData();
+                    }).catch(res => {
+                    });
+                }
+            } else {
+                util.warningMsg("请填写标题");
+            }
+        },
+        handleSelectionChange(val) {
+            this.multipleSelection = val;
+        },
+        onSearch() {
+            this.pageNo = 1;
+            this.getData();
+        },
+        handleCurrentChange(val) {
+            this.pageNo = val;
+            this.getData();
+        },
+        handleDelete(row, index) {
+            this.$confirm("此删除操作不可逆,是否确认删除选中项?", "提示", {
+                type: "warning"
+            }).then(() => {
+                if(row.id === "") {
+                    this.listData.splice(index, 1)
+                    util.successMsg("删除成功");
+                }else {
+                    this.touchTime = this.touchTime+1
+                    this.$del(`${this.api.deleteCompetitionProgress}?competitionProgressId=${row.id}`).then(res => {
+                        util.successMsg("删除成功");
+                        this.getData();
+                    }).catch(res => {
+                    });
+                }
+                
+            }).catch(() => {
+            });
+        },
+        addData() {
+            // this.$store.commit("activity/setWait", 1);
+            this.touchTime = this.touchTime+1
+            if (this.listData.length) {
+                if (this.listData[this.listData.length - 1].id) {
+                    this.listData.push({
+                        competitionId: this.id,
+                        id: "",
+                        title: "",
+                        description: "",
+                        status: 0,
+                        operate: true
+                    });
+                    this.operateIt(this.listData[this.listData.length - 1])
+                } else {
+                    util.warningMsg("请先保存新数据");
+                }
+            } else {
+                this.listData.push({
+                    competitionId: this.id,
+                    id: "",
+                    title: "",
+                    description: "",
+                    status: 1, // status为1时表示未保存的
+                    operate: true
+                });
+            }
+        },
+        waitSave() {
+            if(this.hasEdit) {
+                this.$confirm('暂未保存,是否保存本次编辑?', '提示', {
+                    confirmButtonText: '确定',
+                    cancelButtonText: '取消',
+                    type: 'warning'
+                }).then(() => {
+                    let num = 0;
+                    for(var i=0;i<this.listData.length;i++){
+                        let data = this.listData[i];
+                        if (data.title) {
+                            if (this.listData[i].id) {
+                                this.$put(this.api.editContestProgress, data).then(res => {
+
+                                }).catch(res => {
+                                });
+                            }else{
+                                num = num+1
+                            }
+                        }else{
+                            num = num+1
+                        }
+                    }
+                    if (num >0){
+                        this.$message({
+                            type: 'error',
+                            message: '保存失败,有未填项目'
+                        });
+                    }else{
+                        this.$message({
+                            type: 'success',
+                            message: '保存成功!'
+                        });
+                    }
+                }).catch(() => {
+                    this.$message({
+                        type: 'info',
+                        message: '已取消保存'
+                    });
+                });
+            }
+            
+        }
+    },
+    computed: {
+        hasEdit() {
+            return this.listData.some(item => item.operate)
+        }
+    }
+};
+</script>
+
+<style scoped lang="scss">
+.box {
+    height: calc(100vh - 100px);
+    overflow: auto;
+}
+
+.plus {
+    padding: 15px 0 0;
+    text-align: center;
+    cursor: pointer;
+
+    i {
+        font-size: 24px;
+        color: #cb221c;
+    }
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/activity/manage/manage/matchSignup.vue b/src/pages/activity/manage/manage/matchSignup.vue
new file mode 100644
index 0000000..0b95e1d
--- /dev/null
+++ b/src/pages/activity/manage/manage/matchSignup.vue
@@ -0,0 +1,171 @@
+<template>
+    <!-- 报名人员 -->
+    <div class="page-content" style="padding: 24px">
+        <div class="tool">
+            <ul class="filter">
+                <li>
+                    <label>搜索:</label>
+                    <el-input placeholder="请输入姓名/手机号/学号" prefix-icon="el-icon-search" v-model="keyword" clearable size="mini" style="width: 250px"></el-input>
+                </li>
+                <li v-if="info.releaseType">
+                    <label>参赛人员状态:</label>
+                    <el-select v-model="isDisable" @change="initData">
+                      <el-option v-for="(item, i) in statusList" :key="i" :label="item.name" :value="item.id"></el-option>
+                    </el-select>
+                </li>
+            </ul>
+            <div>
+                <el-button type="primary" round @click="exportAll" v-auth="'/activity/list:管理:报名人员:批量导出'">批量导出</el-button>
+            </div>
+        </div>
+
+        <el-table ref="table" :data="listData" class="table" stripe header-align="center" @selection-change="handleSelectionChange" row-key="id" @sort-change="sortChange">
+            <el-table-column type="selection" width="80" align="center" :reserve-selection="true"></el-table-column>
+            <el-table-column type="index" width="60" label="序号" align="center">
+                <template slot-scope="scope">
+                    {{ scope.$index + (page - 1) * pageSize + 1 }}
+                </template>
+            </el-table-column>
+            <el-table-column prop="school" label="学校" sortable="custom">
+            </el-table-column>
+            <el-table-column prop="username" label="学生姓名">
+            </el-table-column>
+            <el-table-column prop="workNumber" label="学号">
+            </el-table-column>
+            <el-table-column prop="phone" label="手机号">
+            </el-table-column>
+            <el-table-column label="操作" align="center" width="320">
+                <template slot-scope="scope">
+                  <el-switch
+                      v-auth="'/activity/list:管理:报名人员:禁用'"
+                      v-model="scope.row.isDisable"
+                      :active-text="scope.row.isDisable ? '关' : '开'"
+                      :active-value="0"
+                      :inactive-value="1"
+                      style="margin: 0 10px 0 5px"
+                      @change="switchOff($event,scope.row,scope.$index)"
+                  ></el-switch>
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="pagination">
+            <el-pagination background layout="total, prev, pager, next" :total="total" @current-change="handleCurrentChange" :current-page="page">
+            </el-pagination>
+        </div>
+    </div>
+</template>
+
+<script>
+import util from "@/libs/util";
+import axios from 'axios'
+import Setting from "@/setting";
+export default {
+    name: "matchSignup",
+    data() {
+        return {
+            token: util.local.get(Setting.tokenKey),
+            id: +this.$route.query.id,
+            info: {
+              completeCompetitionSetup: {}
+            },
+            isDisable: '',
+            statusList: [
+              {
+                id: '',
+                name: '不限'
+              },
+              {
+                id: 1,
+                name: '已禁用'
+              },
+              {
+                id: 0,
+                name: '未禁用'
+              }
+            ],
+            keyword: "",
+            listData: [],
+            multipleSelection: [],
+            page: 1,
+            pageSize: 10,
+            total: 0,
+        };
+    },
+    watch: {
+        keyword: function(val) {
+            clearTimeout(this.searchTimer);
+            this.searchTimer = setTimeout(() => {
+                this.initData();
+            }, 500);
+        }
+    },
+    mounted() {
+        this.initData()
+    },
+    methods: {
+        getData() {
+            this.$post(this.api.ApplicantsList, {
+              pageNum: this.page,
+              pageSize: this.pageSize,
+              activityId: this.id,
+              keyWords: this.keyword,
+              isDisable: this.isDisable,
+            }).then(({ data }) => {
+                this.listData = data.records;
+                this.total = data.total;
+            }).catch(res => {
+            });
+        },
+        initData() {
+          this.page = 1
+          this.getData()
+        },
+        handleSelectionChange(val) {
+            this.multipleSelection = val;
+        },
+        handleCurrentChange(val) {
+            this.page = val;
+            this.getData();
+        },
+        switchOff(val, row, index) {
+            this.$put(`${this.api.disableRegistration}?competitionRegistrationId=${row.id}&isDisable=${val}`).then(res => {}).catch(err => {});
+        },
+        exportAll() {
+          const data = this.multipleSelection
+          if (data.length) {
+            data.map((e, i) => e.id = i + 1)
+            axios.post(this.api.exportDataInBatches, data, {
+              headers: {
+                token: this.token
+              },
+              responseType: 'blob'
+            }).then((res) => {
+              util.downloadFileDirect(`报名人员.xls`, new Blob([res.data]))
+            }).catch(res => {})
+          } else {
+            axios.get(`${this.api.excelExport}?activityId=${this.id}`, {
+              headers: {
+                token: this.token
+              },
+              responseType: 'blob'
+            }).then((res) => {
+              util.downloadFileDirect(`报名人员.xls`, new Blob([res.data]))
+            }).catch(res => {})
+          }
+        }
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+/deep/.dia-form {
+  .w-100 {
+    width: 100%;
+  }
+  .tips {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/activity/manage/manage/notice.vue b/src/pages/activity/manage/manage/notice.vue
new file mode 100644
index 0000000..d76c7b3
--- /dev/null
+++ b/src/pages/activity/manage/manage/notice.vue
@@ -0,0 +1,140 @@
+<template>
+    <!-- 报名人员 -->
+    <div class="page-content" style="padding: 24px">
+        <div class="tool" style="justify-content: flex-end">
+          <el-button type="primary" round @click="add" v-auth="'/activity/list:管理:公告通知:新增'">新增</el-button>
+        </div>
+
+        <el-table ref="table" :data="listData" class="table" stripe header-align="center" @selection-change="handleSelectionChange" row-key="id">
+            <el-table-column type="index" width="60" label="序号" align="center">
+                <template slot-scope="scope">
+                    {{ scope.$index + (pageNo - 1) * pageSize + 1 }}
+                </template>
+            </el-table-column>
+            <el-table-column prop="announcementTitle" label="标题名称">
+            </el-table-column>
+            <el-table-column prop="createTime" label="创建时间">
+            </el-table-column>
+            <el-table-column prop="updateTime" label="发布时间">
+            </el-table-column>
+            <el-table-column prop="phone" label="状态">
+              <template slot-scope="scope">
+                {{ scope.row.status ? '已发布' : '草稿' }}
+              </template>
+            </el-table-column>
+            <el-table-column label="操作" align="center" width="250">
+                <template slot-scope="scope">
+                    <el-button type="text" @click="edit(scope.row)" v-auth="'/activity/list:管理:公告通知:编辑'">编辑</el-button>
+                    <el-button type="text" @click="del(scope.row)" v-auth="'/activity/list:管理:公告通知:删除'">删除</el-button>
+                    <el-switch
+                        v-auth="'/activity/list:管理:公告通知:启用'"
+                        v-model="scope.row.isOpen"
+                        :active-text="scope.row.isOpen ? '关' : '开'"
+                        :active-value="0"
+                        :inactive-value="1"
+                        style="margin: 0 10px 0 5px"
+                        @change="switchOff($event,scope.row,scope.$index)"
+                    ></el-switch>
+                </template>
+            </el-table-column>
+        </el-table>
+        <div class="pagination">
+            <el-pagination background layout="total, prev, pager, next" :total="totals" @current-change="handleCurrentChange" :current-page="pageNo">
+            </el-pagination>
+        </div>
+    </div>
+</template>
+
+<script>
+import util from "@/libs/util";
+import Setting from "@/setting";
+
+export default {
+    name: "matchSignup",
+    data() {
+        return {
+            token: util.local.get(Setting.tokenKey),
+            id: this.$route.query.id,
+            keyword: "",
+            listData: [],
+            multipleSelection: [],
+            pageNo: 1,
+            pageSize: 10,
+            totals: 0
+        };
+    },
+    watch: {
+        keyword: function(val) {
+            clearTimeout(this.searchTimer);
+            this.searchTimer = setTimeout(() => {
+                this.getData();
+            }, 500);
+        }
+    },
+    mounted() {
+        this.getData()
+    },
+    methods: {
+        getData() {
+          this.$post(`${this.api.queryAnnouncementByCompetitionId}?pageNum=${this.pageNo}&pageSize=${this.pageSize}&competitionId=${this.id}`).then(({ data }) => {
+              this.listData = data.records
+              this.totals = data.total
+              this.$refs.table.clearSelection()
+          }).catch(res => {})
+        },
+        handleSelectionChange(val) {
+            this.multipleSelection = val;
+        },
+        handleCurrentChange(val) {
+            this.pageNo = val;
+            this.getData();
+        },
+        del(row) {
+            this.$confirm("此删除操作不可逆,是否确认删除选中项?", "提示", {
+                type: "warning"
+            })
+                .then(() => {
+                    this.$post(`${this.api.deleteAnnouncement}?id=${row.id}`).then(res => {
+                        util.successMsg("删除成功");
+                        this.getData();
+                    }).catch(res => {
+                    });
+                })
+                .catch(() => {
+                });
+        },
+        switchOff(val, row, index) {
+          if (val) {
+            this.$put(`${this.api.disableAnnouncement}?id=${row.id}&isDisable=${val}`).then(res => {}).catch(err => {})
+          } else if (!row.status) {
+            this.$confirm('是否发布该公告?', '提示', {
+              type: 'success'
+            }).then(() => {
+              this.$put(`${this.api.disableAnnouncement}?id=${row.id}&isDisable=${val}`).then(res => {
+                this.$post(this.api.amendmentAnnouncement, {
+                  id: row.id,
+                  status: 1
+                }).then(res => {
+                  this.getData()
+                }).catch(err => {})
+              }).catch(err => {})
+            }).catch(() => {
+              row.isOpen = 1
+            })
+          } else {
+            this.$put(`${this.api.disableAnnouncement}?id=${row.id}&isDisable=${val}`).then(res => {}).catch(err => {})
+          }
+        },
+        add() {
+          this.$router.push(`noticeDetail?competitionId=${this.id}`)
+        },
+        edit(row) {
+          this.$router.push(`noticeDetail?id=${row.id}&competitionId=${this.id}`)
+        }
+    }
+};
+</script>
+
+<style scoped>
+
+</style>
\ No newline at end of file
diff --git a/src/pages/activity/manage/manage/noticeDetail.vue b/src/pages/activity/manage/manage/noticeDetail.vue
new file mode 100644
index 0000000..519d4f4
--- /dev/null
+++ b/src/pages/activity/manage/manage/noticeDetail.vue
@@ -0,0 +1,308 @@
+<template>
+    <!-- 大赛详情 -->
+    <div>
+      <el-card shadow="hover" style="margin-bottom: 20px">
+          <div class="flex-between">
+              <el-page-header @back="back" :content="(form.id ? '编辑' : '创建') + '公告'"></el-page-header>
+          </div>
+      </el-card>
+      <div class="page">
+          <div class="page-content">
+              <el-form label-width="170px" label-suffix=":" size="small">
+                  <el-form-item label="公告标题">
+                      <div class="d-inline-block">
+                          <el-input placeholder="请输入公告名称" v-model="form.announcementTitle" clearable></el-input>
+                      </div>
+                  </el-form-item>
+                  <el-form-item label="正文">
+                      <quill :border="true" v-model="form.announcementText" :height="400" />
+                  </el-form-item>
+                  <el-form-item label="附件">
+                    <el-upload
+                        :on-remove="handleRemove"
+                        :on-error="uploadError"
+                        :on-success="uploadSuccess"
+                        :before-upload="beforeUpload"
+                        :before-remove="beforeRemove"
+                        :limit="5"
+                        :on-exceed="handleExceed"
+                        :action="this.api.fileupload"
+                        :headers="headers"
+                        :file-list="fileList"
+                        name="file"
+                    >
+                      <el-button size="small" type="primary">点击上传</el-button>
+                      <div slot="tip" class="el-upload__tip">
+                        <p>支持扩展名:.rar .zip .doc .docx .pdf .jpg...</p>
+                      </div>
+                    </el-upload>
+                  </el-form-item>
+                  <el-form-item>
+                      <el-button v-if="!form.id" @click="save(0)" v-auth="'/activity/list:管理:公告通知:草稿'">草稿</el-button>
+                      <el-button type="primary" @click="save(1)" v-auth="'/activity/list:管理:公告通知:发布'">发布</el-button>
+                      <el-button @click="back">取消</el-button>
+                  </el-form-item>
+              </el-form>
+          </div>
+      </div>
+    </div>
+</template>
+
+<script>
+import quill from "@/components/quill";
+import util from "@/libs/util";
+import Setting from "@/setting";
+export default {
+    name: "matchDetail",
+    data() {
+        return {
+            headers: {
+                token: util.local.get(Setting.tokenKey)
+            },
+            form: {
+              id: this.$route.query.id,
+              competitionId: this.$route.query.competitionId,
+              announcementText: '',
+              announcementTitle: '',
+              announcementAnnexList: [],
+              isOpen: 1
+            },
+            updateTime: 0,
+            fileName: '',
+            fileList: [],
+        };
+    },
+    components: {
+        quill
+    },
+    watch: {
+        // 监听信息是否有更改,有的话页面离开的时候要询问是否要保存
+        form: {
+            handler(){
+                this.updateTime++
+                if(this.updateTime > 1) this.$store.commit('activity/setWait', 0)
+            },
+            deep:true
+        },
+    },
+    mounted() {
+        this.form.id && this.getData()
+    },
+    methods: {
+        getData() {
+          this.$post(`${this.api.queryAnnouncementDetails}?id=${this.form.id}`).then(({ data }) => {
+            this.form = data
+            // 附件
+            const fileList = data.announcementAnnexList
+            if (fileList) {
+              const files = []
+              fileList.map(e => {
+                files.push({
+                  name: e.fileName,
+                  url: e.filePath
+                })
+              })
+              this.fileList = files
+            } else {
+              data.announcementAnnexList = []
+            }
+          }).catch(err => {})
+        },
+        // 保存
+        save(status) {
+            const form = this.form
+            if (!form.announcementTitle) return util.warningMsg('请填写公告标题')
+            if (!form.announcementText) return util.warningMsg('请填写正文')
+            form.status = status
+            if (form.id) {
+                form.isOpen = 0
+                delete form.announcementAnnexList
+                this.$post(this.api.amendmentAnnouncement, form).then(res => {
+                    util.successMsg("修改成功")
+                    this.$router.back()
+                }).catch(err => {})
+            } else {
+              form.isOpen = status ? 0 : 1
+              this.$post(this.api.addAnnouncement, form).then(res => {
+                util.successMsg("创建成功")
+                this.$router.back()
+              }).catch(err => {})
+            }
+        },
+        handleExceed(files, fileList) {
+            util.warningMsg(`当前限制选择 1 个文件,如需更换,请删除上一个文件再重新选择!`);
+        },
+        // 附件上传成功
+        uploadSuccess(res) {
+          const file = res.data.filesResult
+          const { id } = this.form
+          const data = {
+            announcementId: id || '',
+            fileName: this.fileName,
+            filePath: file.fileUrl || file.fileId
+          }
+          this.form.announcementAnnexList.push(data)
+          // 编辑的时候需要调新增附件接口
+          id && this.$post(this.api.saveAnnouncementAnnex, data).then(res => {}).catch(res => {})
+        },
+        // 附件上传前
+        beforeUpload(file) {
+          this.fileName = file.name
+        },
+        uploadError(err, file, fileList) {
+            this.$message({
+                message: "上传出错,请重试!",
+                type: "error",
+                center: true
+            });
+        },
+        beforeRemove(file, fileList) {
+            return this.$confirm(`确定移除 ${file.name}?`);
+        },
+        handleRemove(file, fileList) {
+          if (file.url) {
+            this.$del(`${this.api.fileDeletion}?keys=${file.url}`).then(res => {}).catch(res => {})
+            const id = this.form.announcementAnnexList.find(e => e.fileName === file.name).id
+            this.$post(`${this.api.delAnnex}?id=${id}`).then(res => {}).catch(res => {})
+          }
+        },
+        back() {
+          this.$router.back()
+        }
+    }
+};
+</script>
+
+<style scoped lang="scss">
+$upload-width: 220px;
+$upload-height: 140px;
+$upload-lg-height: 150px;
+/deep/ .avatar-uploader {
+    .el-upload {
+        position: relative;
+        width: $upload-width;
+        height: $upload-height;
+        border: 1px dashed #d9d9d9;
+        border-radius: 6px;
+        cursor: pointer;
+        overflow: hidden;
+
+        &:hover {
+            border-color: #cb221c;
+        }
+
+        .uploader-default {
+            display: flex;
+            height: $upload-height;
+            flex-direction: column;
+            justify-content: center;
+            text-align: center;
+            background: rgba(0, 0, 0, 0.04);
+
+            i {
+                font-size: 20px;
+                font-weight: bold;
+                color: #8c939d;
+            }
+
+            p {
+                margin-top: 10px;
+                font-size: 14px;
+                color: rgba(0, 0, 0, 0.65);
+                line-height: 1;
+            }
+        }
+    }
+
+    &.avatar-uploader-lg {
+        .el-upload {
+            width: 100%;
+            max-width: 960px;
+            height: $upload-lg-height;
+
+            .uploader-default {
+                height: $upload-lg-height;
+            }
+        }
+    }
+
+    .avatar {
+        display: block;
+        width: $upload-width;
+        height: $upload-height;
+    }
+
+    .avatar-lg {
+        display: block;
+        width: 100%;
+        height: $upload-lg-height;
+    }
+
+    .el-upload__tip {
+        margin-top: 0;
+
+        p {
+            font-size: 14px;
+            color: rgba(0, 0, 0, 0.45);
+            line-height: 1;
+
+            &:first-child {
+                margin-bottom: 5px;
+            }
+        }
+    }
+}
+
+/deep/ .d-inline-block {
+    width: 216px;
+
+    .el-select, .el-input {
+        width: 100%;
+    }
+}
+
+.inline-input {
+    .input-wrap {
+        display: flex;
+        align-items: center;
+        margin-bottom: 10px;
+
+        .el-input {
+            display: inline-block;
+            width: 216px;
+            margin-right: 8px;
+        }
+
+        .remove {
+            width: 16px;
+            height: 16px;
+            background: url("../../../assets/img/close.png") 0 0/cover no-repeat;
+            cursor: pointer;
+        }
+    }
+
+    .add-btn {
+        margin-left: 32px;
+    }
+}
+
+.add-btn {
+    display: flex;
+    justify-content: center;
+    align-items: center;
+    width: 216px;
+    line-height: 32px;
+    font-size: 14px;
+    color: rgba(0, 0, 0, 0.65);
+    background-color: transparent;
+    border: 1px dashed rgba(0, 0, 0, 0.15);
+    border-radius: 4px;
+    cursor: pointer;
+
+    i {
+        margin-right: 8px;
+        font-size: 14px;
+        font-weight: bold;
+    }
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/activity/manage/preview/index.vue b/src/pages/activity/manage/preview/index.vue
new file mode 100644
index 0000000..12b2f85
--- /dev/null
+++ b/src/pages/activity/manage/preview/index.vue
@@ -0,0 +1,533 @@
+<template>
+    <div class="activity">
+      <div class="banner" :style="{backgroundImage: 'url(' + (form.carouselUrl || 'https://huoran.oss-cn-shenzhen.aliyuncs.com/20220613/png/1536269450851409920.png') + ')'}"></div>
+      <div class="center">
+        <breadcrumb ref="breadcrumb" :data="'全部赛事/' + form.name" route="matchPreview"></breadcrumb>
+        <div class="activity-inner">
+          <div class="flex-between">
+              <el-tabs v-model="curType" @tab-click="typeChange">
+                  <el-tab-pane v-for="(item, index) in typeList" :key="index" :label="item.name" :name="item.id"></el-tab-pane>
+              </el-tabs>
+              <div class="status wait">等待报名</div>
+          </div>
+          <div class="info">
+            <h6 class="title">{{ form.name }}</h6>
+            <div class="meta">最近编辑时间:{{ form.updateTime }}</div>
+          </div>
+
+          <div class="l-title" id="part1"><img src="@/assets/img/label.png" alt=""> 竞赛信息</div>
+          <div v-if="form.description" class="texts ql-editor" v-html="form.description"></div>
+          <template v-if="form.competitionAnnexList && form.competitionAnnexList.length">
+            <h6 class="p-title">附件下载</h6>
+            <ul class="files">
+              <li v-for="(item, i) in form.competitionAnnexList" :key="i">
+                <el-link v-if="item.canPreview" class="m-r-10" type="primary" @click="previewFile(item)">{{ item.fileName }}</el-link>
+                <span v-else class="file-name">{{ item.fileName }}</span>
+                <el-link type="primary" :underline="false" @click="download(item)">下载</el-link>
+              </li>
+            </ul>
+          </template>
+          <template v-if="!form.description && (!form.competitionAnnexList || !form.competitionAnnexList.length )">
+              <div class="empty">
+                  <div>
+                      <img src="@/assets/img/none.png" alt="">
+                      <p>暂无数据</p>
+                  </div>
+              </div>
+          </template>
+
+          <template v-if="form.releaseType && form.competitionStage.length">
+            <div class="l-title"><img src="@/assets/img/label.png" alt=""> 赛程、规则与内容</div>
+            <h6 class="rule-title">共{{ form.competitionStage.length }}个竞赛阶段,同一个团队每个成员只能参加一个阶段赛项</h6>
+            <div v-for="(rule, i) in form.competitionStage" :key="i" class="rule">
+              <p style="font-size: 16px;color: #333;">{{ rule.stageName }}</p>
+              <p>比赛时间:{{ rule.startTime && rule.startTime + ' ~ ' + rule.endTime }}</p>
+              <p>比赛方式:{{ methods.find(e => e.id == rule.method) && methods.find(e => e.id == rule.method).name }}</p>
+              <p v-if="!rule.method">课程系统:{{ rule.systemName }}</p>
+              <p v-if="rule.onlineButton">线上地点:{{ rule.onlineAddress }}</p>
+              <p v-if="rule.offlineButton">线下地点:{{ rule.offlineAddress }}</p>
+              <template v-if="rule.method === 2">
+                <p>线下地点:{{ rule.offlineAddress }}</p>
+                <p>比赛内容:{{ rule.contentDescription }}</p>
+                <p>评分规则:{{ rule.scoreRule }}</p>
+              </template>
+              
+              <template v-if="form.completeCompetitionSetup.competitionType">
+                <p>团队参赛人数限制:{{ rule.teamNumLimit || '不限制' }}</p>
+                <p>团队成绩计算方式:{{ teamCalculationMethods.find(e => e.id == rule.teamCalculationMethod) && teamCalculationMethods.find(e => e.id == rule.teamCalculationMethod).name }}</p>
+              </template>
+              <p>阶段比赛结束后{{ rule.resultAnnouncementTime }}小时,公布阶段比赛成绩。</p>
+              <div v-if="form.rule === 1" class="flex">
+                <p>晋级规则:</p>
+                <div>
+                  <p v-if="rule.peopleLimit">本阶段成绩排名前{{ rule.peopleLimit }}队,可晋级下一阶段比赛</p>
+                  <p v-if="rule.percentageLimit">本阶段成绩排名前{{ rule.percentageLimit }}%,可晋级下一阶段比赛</p>
+                  <p v-if="rule.scoreLimit">本阶段成绩{{ rule.scoreLimit }}分,可晋级下一阶段比赛</p>
+                </div>
+              </div>
+            </div>
+          </template>
+
+          <!-- 进展 -->
+          <div class="l-title" id="part2"><img src="@/assets/img/label.png" alt=""> 竞赛进展</div>
+          <ul class="progress" v-if="progress.length">
+              <li v-for="(item,index) in progress" :key="index" :class="item.status == 0 ? 'not' : (item.status == 1 ? 'ing' : 'done')">
+                  <i class="dot"></i>
+                  <p class="name">{{item.title}}</p>
+                  <p class="desc">{{item.description}}</p>
+              </li>
+              <img class="rocket" src="@/assets/img/rocket.png" alt="">
+          </ul>
+          <template v-else>
+              <div class="empty">
+                  <div>
+                      <img src="@/assets/img/none.png" alt="">
+                      <p>暂无数据</p>
+                  </div>
+              </div>
+          </template>
+          
+          <!-- 公告 -->
+          <div class="l-title" id="part3"><img src="@/assets/img/label.png" alt=""> 通知公告</div>
+          <ul class="notice-list" v-if="notices.length">
+            <li v-for="(item, i) in notices" :key="i" @click="toNotice(item)">
+              <h6>{{ item.announcementTitle }}</h6>
+              <p class="meta">{{ item.updateTime }}</p>
+              <div class="des" v-html="item.announcementText"></div>
+            </li>
+          </ul>
+          <template v-else>
+              <div class="empty">
+                  <div>
+                      <img src="@/assets/img/none.png" alt="">
+                      <p>暂无通知公告</p>
+                  </div>
+              </div>
+          </template>
+        </div>
+      </div>
+    </div>
+</template>
+
+<script>
+import util from "@/libs/util";
+import breadcrumb from '@/components/breadcrumb'
+import Const from '@/const/activity'
+export default {
+    data() {
+        return {
+          rules: Const.rules,
+          methods: Const.methods,
+          teamCalculationMethods: Const.teamCalculationMethods,
+          curType: '1',
+          typeList: [
+              {
+                  id: '1',
+                  name: '竞赛信息'
+              },
+              {
+                  id: '2',
+                  name: '竞赛进展'
+              },
+              {
+                  id: '3',
+                  name: '通知公告'
+              }
+          ],
+          form: util.local.get('activity'),
+          progress: [],
+          notices: [],
+        };
+    },
+    components: {
+        breadcrumb
+    },
+    mounted() {
+      this.handleAnnex()
+      if (this.form.id) {
+        this.getProgress()
+        this.getNotice()
+      }
+    },
+    methods: {
+      // 处理附件
+      handleAnnex() {
+        const list = this.form.competitionAnnexList
+        if (list) {
+          list.map(e => {
+            const { filePath } = e
+            e.canPreview = util.canPreview(filePath.substr(filePath.lastIndexOf('.') + 1)) // 判断是否能够预览
+          })
+          this.$forceUpdate()
+        }
+      },
+      getProgress() { // 获取竞赛进展
+          this.$get(this.api.getCompetitionProgress, {
+            competitionId: this.form.id
+          }).then(res => {
+              this.progress = res.competitionProgressList.reverse()
+          }).catch(err => {});
+      },
+      // 公告列表
+      getNotice() {
+        this.$post(`${this.api.queryAnnouncementByCompetitionId}?pageNum=1&pageSize=1000&competitionId=${this.form.id}`).then(({ data }) => {
+          const records = data.records.filter(e => e.status) // 只显示已发布的(status 0草稿 1为已发布)
+          records.map(e => {
+            e.announcementText = e.announcementText.replace(/<img.*?(?:>|\/>)/gi, '')
+          })
+          this.notices = records
+        }).catch(res => {})
+      },
+      // 预览附件
+      previewFile(item) {
+        const { filePath } = item
+        const suffix = filePath.substr(filePath.lastIndexOf('.') + 1)
+        window.open((util.isDoc(suffix) || suffix === 'pdf' ? 'https://view.officeapps.live.com/op/view.aspx?src=' : '') + item.filePath)
+      },
+      // 下载附件
+      download(item) {
+        util.downloadFile(item.fileName, item.filePath)
+      },
+      // tab切换
+      typeChange() {
+        document.querySelector(`#part${this.curType}`).scrollIntoView()
+      },
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+.activity {
+  padding-bottom: 20px;
+  background-color: #F3F6FA;
+  .banner{
+    width: 100%;
+    height: 350px;
+    color: #fff;
+    background-size: 100% 350px;
+    background-repeat: no-repeat;
+    box-sizing: border-box;
+  }
+  .center {
+    width: 1000px;
+    margin: 40px auto 0;
+  }
+  .activity-inner {
+    min-height: calc(100vh - 465px);
+    padding: 30px 40px 20px;
+    background-color: #fff;
+    box-sizing: border-box;
+  }
+  /deep/.el-tabs {
+    .el-tabs__item.is-active, .el-tabs__item:hover {
+      color: #007EFF;
+    }
+    .el-tabs__active-bar {
+      background-color: #007EFF;
+    }
+  }
+  .p-title {
+    border-left-color: #007EFF;
+  }
+  .l-title{
+    display: flex;
+    align-items: center;
+    margin-bottom: 12px;
+    font-size: 18px;
+    color: #333;
+    img{
+      margin-right: 5px;
+    }
+  }
+  .status {
+      padding: 0 16px;
+      margin-left: 20px;
+      line-height: 34px;
+      font-size: 14px;
+      color: #fff;
+      background-color: #52C41A;
+      border-radius: 4px;
+      cursor: pointer;
+      &.wait {
+          background-color: #FAAD14;
+      }
+      &.signing {
+          background-color: #007EFF;
+      }
+      &.signed {
+          background-color: #52C41A;
+      }
+      &.finish {
+          background-color: #ccc;
+      }
+    }
+  .title{
+      width: 67%;
+      margin: 0 auto;
+      font-size: 28px;
+      text-align: center;
+      color: #0B1D30;
+  }
+  .info .meta{
+      padding: 16px 0;
+      font-size: 12px;
+      color: #999;
+      text-align: center;
+  }
+  .texts {
+    margin-bottom: 30px;
+    font-size: 14px;
+    line-height: 1.6;
+    text-indent: 2em;
+    overflow: hidden;
+    /deep/img{
+        max-width: 100%;
+    }
+  }
+  .files {
+    margin-bottom: 30px;
+    li {
+      display: flex;
+      align-items: center;
+      margin: 10px 0;
+    }
+    .file-name {
+      margin-right: 10px;
+      font-size: 12px;
+    }
+  }
+  .el-link.el-link--primary {
+    color: #007EFF !important;
+    &:after {
+      border-color: #007EFF;
+    }
+  }
+}
+.progress{
+    position: relative;
+    width: 95%;
+    padding: 50px 0;
+    margin: 40px auto 80px;
+    text-align: left;
+    &:before{
+        content: '';
+        position: absolute;
+        top: 0;
+        left: 50%;
+        width: 2px;
+        height: 100%;
+        background-color: #E1E6F2;
+    }
+    &:after {
+        content: '';
+        position: absolute;
+        top: -10px;
+        left: 430px;
+        border: 8px solid transparent;
+        border-bottom-color: #E1E6F2;
+    }
+    .rocket {
+        position: absolute;
+        bottom: -50px;
+        left: 425px;
+    }
+    li{
+        position: relative;
+        width: 400px;
+        margin-bottom: 42px;
+        .dot{
+            position: absolute;
+            top: 12px;
+            left: 431px;
+            width: 15px;
+            height: 15px;
+            background-color: #DCDCDC;
+            border-radius: 50%;
+        }
+        .name{
+            display: inline-block;
+            padding: 0 19px;
+            margin-bottom: 16px;
+            line-height: 40px;
+            text-align: center;
+            font-size: 16px;
+            color: #fff;
+            border-radius: 20px;
+            background-color: #C4C4C4;
+        }
+        .desc{
+            position: relative;
+            color: #333;
+            font-size: 14px;
+        }
+        &.ing, &.done {
+            .dot {
+                top: 8px;
+                background-color: #007EFF;
+            }
+            .name {
+                background-color: #007EFF;
+            }
+        }
+        &.ing {
+            .dot {
+                width: 27px;
+                height: 27px;
+                border: 6px solid #E2F1FB;
+            }
+        }
+        &:nth-child(odd) {
+            text-align: right;
+            &.ing {
+                .dot {
+                    left: auto;
+                    right: -58px;
+                }
+            }
+            .name {
+                &:before {
+                    content: '';
+                    z-index: 2;
+                    position: absolute;
+                    top: 14px;
+                    right: -35px;
+                    border: 18px solid transparent;
+                    border-top-width: 6px;
+                    border-bottom-width: 6px;
+                    border-left-color: #C4C4C4;
+                }
+            }
+            .desc {
+                text-align: right;
+            }
+            &.ing, &.done {
+                .name {
+                    &:before {
+                        border-left-color: #007EFF;
+                    }
+                }
+            }
+        }
+        &:nth-child(even) {
+            margin-left: 482px;
+            .dot {
+                left: -51px;
+            }
+            &.ing {
+                .dot {
+                    left: -57px;
+                }
+            }
+            .name {
+                text-align: left;
+                &:after {
+                    content: '';
+                    z-index: 2;
+                    position: absolute;
+                    top: 14px;
+                    left: -35px;
+                    border: 18px solid transparent;
+                    border-top-width: 6px;
+                    border-bottom-width: 6px;
+                    border-right-color: #C4C4C4;
+                }
+            }
+            .desc {
+                &:before {
+                    left: auto;
+                    right: -16px;
+                    border: 8px solid transparent;
+                    border-left-color: #fff;
+                }
+                &:after {
+                    left: auto;
+                    right: -18px;
+                    border: 9px solid transparent;
+                    border-left-color: #E6E6E6;
+                }
+            }
+            
+            &.ing, &.done {
+                .name {
+                    &:after {
+                        border-right-color: #007EFF;
+                    }
+                }
+            }
+        }
+        &:last-child{
+            margin-bottom: 0;
+        }
+    }
+}
+.notice-list {
+  text-align: left;
+  li {
+    padding: 16px;
+    margin-bottom: 12px;
+    transition: all 0.3s;
+    cursor: pointer;
+    border-radius: 6px;
+    background-color: #fff;
+    border-bottom: 1px dashed #ebebeb;
+    &:last-child {
+      border-bottom: 0;
+    }
+  }
+  h6 {
+    font-size: 20px;
+    font-weight: 500;
+    color: #0B1D30;
+    &:hover {
+      color: #007EFF;
+    }
+  }
+  .meta {
+    margin: 10px 0;
+    font-size: 14px;
+    color: #666;
+  }
+  .des {
+    font-size: 14px;
+    color: #333;
+    line-height: 24px;
+    display: -webkit-box;
+    display:-moz-box;
+    -webkit-box-orient: vertical;
+    -moz-box-orient: vertical;
+    -webkit-line-clamp: 2;
+    -moz-line-clamp: 2;
+    overflow: hidden;
+    text-overflow: ellipsis;
+  }
+}
+.empty{
+  display: flex;
+  justify-content: center;
+  align-items: center;
+  padding: 50px 0;
+  text-align: center;
+  img{
+    width: 471px;
+  }
+  p{
+    margin-top: 40px;
+    font-size: 18px;
+    color: rgba(0, 0, 0, 0.25);
+  }
+}
+.rule-title {
+  margin-bottom: 10px;
+  font-size: 16px;
+}
+.rule {
+  padding: 15px;
+  margin-bottom: 15px;
+  border: 1px solid #dfdfdf;
+  p {
+    font-size: 14px;
+    line-height: 30px;
+    color: #6e6e6e;
+  }
+}
+</style>
\ No newline at end of file
diff --git a/src/pages/activity/noticeDetail/index.vue b/src/pages/activity/noticeDetail/index.vue
new file mode 100644
index 0000000..552f54a
--- /dev/null
+++ b/src/pages/activity/noticeDetail/index.vue
@@ -0,0 +1,170 @@
+<template>
+    <div class="wrap">
+        <div class="breadcrumb">
+            <el-breadcrumb separator=">">
+            <template v-for="(item, i) in breadPath">
+                <el-breadcrumb-item
+                v-if="!i"
+                :key="i"
+                :to="{ path: 'list' }">
+                {{item}}
+                </el-breadcrumb-item>
+                <el-breadcrumb-item
+                v-else-if="breadPath.length > 2 && i === 1"
+                :to="{ path: 'details', query: { end, status, id } }"
+                :key="i">
+                {{item}}
+                </el-breadcrumb-item>
+                <el-breadcrumb-item
+                v-else
+                :key="i">
+                {{item}}
+                </el-breadcrumb-item>
+            </template>
+            </el-breadcrumb>
+        </div>
+        <div class="page">
+            <h6 class="title">{{ form.announcementTitle }}</h6>
+            <div class="metas">
+              <span>{{ form.updateTime }}</span>
+            </div>
+            <div class="content ql-editor" v-html="form.announcementText"></div>
+            
+            <template v-if="form.announcementAnnexList">
+              <h6 class="p-title">附件下载</h6>
+              <ul class="files">
+                <li v-for="(item, i) in form.announcementAnnexList" :key="i">
+                  <el-link class="m-r-10" type="primary" @click="preview(item)">{{ item.fileName }}</el-link>
+                  <el-link type="primary" :underline="false" @click="download(item)">下载</el-link>
+                </li>
+              </ul>
+            </template>
+        </div>
+    </div>
+</template>
+<script>
+import { Loading } from 'element-ui';
+import 'quill/dist/quill.core.css';
+import 'quill/dist/quill.snow.css';
+import 'quill/dist/quill.bubble.css';
+import breadcrumb from '@/components/breadcrumb'
+import util from '@/libs/util'
+export default {
+    data() {
+        return {
+            id: this.$route.query.matchId,
+            end: this.$route.query.end,
+            status: this.$route.query.status,
+            breadPath: ['全部赛事', this.$route.query.name],
+            form: {
+              id: this.$route.query.id,
+              announcementText: '',
+              announcementTitle: '',
+              announcementAnnexList: []
+            },
+            loadIns: null
+        }
+    },
+    components: {
+        breadcrumb
+    },
+    mounted() {
+        this.getData()
+    },
+    methods: {
+        // 公告详情
+        getData() {
+          this.loadIns = Loading.service()
+          this.$post(`${this.api.queryAnnouncementDetails}?id=${this.form.id}`).then(({ data }) => {
+            this.form = data
+            this.breadPath.push(data.announcementTitle)
+            this.loadIns.close()
+          }).catch(err => {
+            this.loadIns.close()
+          })
+        },
+        // 预览附件
+        preview(item) {
+          const { filePath } = item
+          const suffix = filePath.substr(filePath.lastIndexOf('.') + 1)
+          window.open((util.isDoc(suffix) || suffix === 'pdf' ? 'https://view.officeapps.live.com/op/view.aspx?src=' : '') + item.filePath)
+        },
+        // 下载附件
+        download(item) {
+          util.downloadFile(item.fileName, item.filePath)
+        }
+    }
+};
+</script>
+
+<style lang="scss" scoped>
+.breadcrumb {
+  margin: 4px 0 16px;
+  /deep/.el-breadcrumb__item {
+    .is-link, .el-breadcrumb__separator {
+      font-weight: 400;
+      color: $main-color;
+    }
+    &:last-child {
+      .is-link {
+        color: #0B1D30;
+      }
+    }
+  }
+}
+.wrap {
+    padding-bottom: 20px;
+    .title{
+        margin-top: 30px;
+        text-align: center;
+        font-size: 28px;
+        font-weight: 500;
+        color: #0B1D30;
+    }
+    .metas{
+        display: flex;
+        justify-content: center;
+        align-items: center;
+        padding-bottom: 32px;
+        margin: 16px 0 32px;
+        span{
+            display: inline-flex;
+            align-items: center;
+            color: #999;
+            font-size: 12px;
+            img{
+                width: 18px;
+                margin-right: 5px;
+            }
+        }
+        .el-divider {
+            margin: 0 16px;
+        }
+    }
+    .cover{
+        margin: 20px 0;
+        text-align: center;
+        img{
+            width: 800px;
+        }
+    }
+    .content{
+        margin-bottom: 20px;
+        line-height: 1.8;
+        font-size: 14px;
+        text-indent: 2em;
+        /deep/img{
+            max-width: 100%;
+        }
+    }
+}
+.files {
+  margin-bottom: 30px;
+  li {
+    display: flex;
+    align-items: center;
+    margin: 10px 0;
+  }
+}
+</style>
+
diff --git a/src/router/modules/activity.js b/src/router/modules/activity.js
new file mode 100644
index 0000000..dd228d6
--- /dev/null
+++ b/src/router/modules/activity.js
@@ -0,0 +1,37 @@
+import BasicLayout from "@/layouts/home";
+
+const meta = {};
+
+const pre = "activity-";
+
+export default {
+    path: "/activity",
+    name: "activity",
+    redirect: {
+        name: `${pre}list`
+    },
+    meta,
+    component: BasicLayout,
+    children: [
+        {
+            path: `list`,
+            component: () => import("@/pages/activity/list"),
+        },
+        {
+            path: `details`,
+            component: () => import("@/pages/activity/details"),
+        },
+        {
+            path: `noticeDetail`,
+            component: () => import("@/pages/activity/noticeDetail"),
+        },
+        {
+            path: `manage`,
+            component: () => import("@/pages/activity/manage/list"),
+        },
+        {
+            path: `add`,
+            component: () => import("@/pages/activity/manage/add"),
+        }
+    ]
+};
\ No newline at end of file