浏览代码

Merge branch 'dev_general_table' into dev

Jinyao 5 年之前
父节点
当前提交
830e0c3d2d

+ 4 - 2
mock/routers/buglist.js

@@ -34,11 +34,13 @@ router.get('/list', (req, res) => {
         ],
         description: 'bug description',
         endTime: Random.datetime(),
-        expectedTimeCost: 140,
+        timeToTake: 140,
         'id|+5': 1001,
         'priority|1': ['紧急', '一般', '宽松'],
+        processorId: 1001,
+        processorName: 'hkshu12',
         startTime: Random.datetime(),
-        'state|1': ['CREATED', 'PROCESSING', 'FINISHED'],
+        'state|1': ['已创建', '进行中', '已完成'],
         title: 'bug titleeee',
       }],
     },

+ 18 - 6
mock/routers/project.js

@@ -46,13 +46,25 @@ router.get('/listByUser/:userId',
     ));
   });
 
-router.get('/delete/:projectId',
-  (req, res) => {
-    res.json(wrapper({}));
-  });
+router.post('/members', (req, res) => {
+  res.json(wrapper('success'));
+});
 
-router.get('/members', (req, res) => {
-  res.json(wrapper([101, 102, 103, 104]));
+router.get('/:projectId/members', (req, res) => {
+  res.json(wrapper(
+    Mock.mock({
+      'data|4': [
+        {
+          email: Random.email('smail.nju.edu.cn'),
+          'id|+1': 1110,
+          phone: 'string',
+          'role|1': ['学生', '教师', '管理员'],
+          'username|1': ['hkshu12', 'cyt123', 'Jinyao'],
+          withGitlab: Random.boolean(),
+        },
+      ],
+    }).data,
+  ));
 });
 
 router.get('/refresh', (req, res) => {

+ 9 - 18
mock/routers/tree.js

@@ -35,14 +35,13 @@ router.get('/node/type/task/:projectId', (req, res) => {
         parent: null,
         'priority|1': ['紧急', '一般', '宽松'],
         processorId: 101,
+        processorName: 'Jinyao',
         projectId: 101,
         startTime: Random.datetime(),
         'state|1': ['已创建', '进行中', '已完成'],
         timeToTake: 10,
         title: Random.cname(),
         'type|1': ['需求', '任务'],
-        userId: 101,
-        username: Random.cname(),
       }],
     }).data,
   ));
@@ -67,14 +66,13 @@ router.get('/:projectId', (req, res) => {
       parent: null,
       'priority|1': ['紧急', '一般', '宽松'],
       processorId: 101,
+      processorName: 'Jinyao',
       projectId: 101,
       startTime: Random.datetime(),
       'state|1': ['已创建', '进行中', '已完成'],
       timeToTake: 10,
       title: Random.cname(),
       'type|1': ['需求', '任务'],
-      userId: 101,
-      username: Random.cname(),
     },
     {
       deep: 2,
@@ -85,14 +83,13 @@ router.get('/:projectId', (req, res) => {
       parent: 1,
       'priority|1': ['紧急', '一般', '宽松'],
       processorId: 101,
+      processorName: 'Jinyao',
       projectId: 101,
       startTime: Random.datetime(),
       'state|1': ['已创建', '进行中', '已完成'],
       timeToTake: 10,
       title: Random.cname(),
       'type|1': ['需求', '任务'],
-      userId: 101,
-      username: Random.cname(),
     },
     {
       deep: 2,
@@ -103,14 +100,13 @@ router.get('/:projectId', (req, res) => {
       parent: 1,
       'priority|1': ['紧急', '一般', '宽松'],
       processorId: 101,
+      processorName: 'Jinyao',
       projectId: 101,
       startTime: Random.datetime(),
       'state|1': ['已创建', '进行中', '已完成'],
       timeToTake: 10,
       title: Random.cname(),
       'type|1': ['需求', '任务'],
-      userId: 101,
-      username: Random.cname(),
     },
     {
       deep: 3,
@@ -121,14 +117,13 @@ router.get('/:projectId', (req, res) => {
       parent: 3,
       'priority|1': ['紧急', '一般', '宽松'],
       processorId: 101,
+      processorName: 'Jinyao',
       projectId: 101,
       startTime: Random.datetime(),
       'state|1': ['已创建', '进行中', '已完成'],
       timeToTake: 10,
       title: Random.cname(),
       'type|1': ['需求', '任务'],
-      userId: 101,
-      username: Random.cname(),
     },
     {
       deep: 3,
@@ -139,14 +134,13 @@ router.get('/:projectId', (req, res) => {
       parent: 3,
       'priority|1': ['紧急', '一般', '宽松'],
       processorId: 101,
+      processorName: 'Jinyao',
       projectId: 101,
       startTime: Random.datetime(),
       'state|1': ['已创建', '进行中', '已完成'],
       timeToTake: 10,
       title: Random.cname(),
       'type|1': ['需求', '任务'],
-      userId: 101,
-      username: Random.cname(),
     },
     {
       deep: 4,
@@ -157,14 +151,13 @@ router.get('/:projectId', (req, res) => {
       parent: 5,
       'priority|1': ['紧急', '一般', '宽松'],
       processorId: 101,
+      processorName: 'Jinyao',
       projectId: 101,
       startTime: Random.datetime(),
       'state|1': ['已创建', '进行中', '已完成'],
       timeToTake: 10,
       title: Random.cname(),
       'type|1': ['需求', '任务'],
-      userId: 101,
-      username: Random.cname(),
     },
     {
       deep: 4,
@@ -175,14 +168,13 @@ router.get('/:projectId', (req, res) => {
       parent: 5,
       'priority|1': ['紧急', '一般', '宽松'],
       processorId: 101,
+      processorName: 'Jinyao',
       projectId: 101,
       startTime: Random.datetime(),
       'state|1': ['已创建', '进行中', '已完成'],
       timeToTake: 10,
       title: Random.cname(),
       'type|1': ['需求', '任务'],
-      userId: 101,
-      username: Random.cname(),
     },
     {
       deep: 4,
@@ -193,14 +185,13 @@ router.get('/:projectId', (req, res) => {
       parent: 5,
       'priority|1': ['紧急', '一般', '宽松'],
       processorId: 101,
+      processorName: 'Jinyao',
       projectId: 101,
       startTime: Random.datetime(),
       'state|1': ['已创建', '进行中', '已完成'],
       timeToTake: 10,
       title: Random.cname(),
       'type|1': ['需求', '任务'],
-      userId: 101,
-      username: Random.cname(),
     },
     ]),
   ));

+ 1 - 1
src/api/devcloud/buglist.ts

@@ -4,7 +4,7 @@ import { BUGLIST_PREFIX } from './prefix';
 
 interface BugRecordPayload {
   description: string;
-  expectedTimeCost: number;
+  timeToTake: number;
   priority: '紧急'|'一般'|'宽松';
   title: string;
 }

+ 2 - 2
src/api/devcloud/project.ts

@@ -16,7 +16,7 @@ export const createProject = (name: string, description: string) => axios.post(`
 
 export const getProjectListByUserId = (userId: number) => axios.get(`${PROJECT_PREFIX}/listByUser/${userId}`);
 
-export const deleteProject = (projectId: string) => axios.get(`${PROJECT_PREFIX}/delete/${projectId}`);
-
 export const addProjectMember = (addProjectMemberPayload: AddProjectMemberPayload) => axios.post(`${PROJECT_PREFIX}/members`,
   addProjectMemberPayload);
+
+export const getProjectMembersByProjectId = (projectId: number) => axios.get(`${PROJECT_PREFIX}/${projectId}/members`);

+ 155 - 0
src/components/GeneralTable.vue

@@ -0,0 +1,155 @@
+<template>
+  <div class="table-prefix">
+    <div class="right">
+      <ElButton class="prefix-right-button" size="small" type="text" @click="clearFilter">清除过滤器</ElButton>
+      <ElButton class="prefix-right-button" v-if="showAdd" size="small" type="primary" @click="add">新建</ElButton>
+    </div>
+  </div>
+  <ElTable
+    ref="tableRef"
+    :data="data"
+    :default-sort="{prop: 'id', order: 'descending'}"
+    @row-click="rowClick"
+  >
+    <ElTableColumn
+      prop="id"
+      label="ID"
+      align="center"
+      width="80"
+    ></ElTableColumn>
+    <ElTableColumn
+      label="优先级"
+      align="center"
+      width="80"
+      :filters="[{ text: '紧急', value: '紧急' }, { text: '一般', value: '一般' }, { text: '宽松', value: '宽松' }]"
+      :filter-method="(value, row) => {
+        return row.priority === value;
+      }"
+      filter-placement="bottom-end"
+    >
+      <template #default="scope">
+        <PriorityTag :priority="scope.row.priority" />
+      </template>
+    </ElTableColumn>
+    <ElTableColumn
+      prop="title"
+      label="名称"
+      align="center"
+      width="180"
+    ></ElTableColumn>
+    <ElTableColumn
+      prop="processorName"
+      label="负责人"
+      align="center"
+      width="120"
+      :filters="[{ text: '我', value: me }]"
+      :filter-method="(value, row) => {
+        return row.processorName === value;
+      }"
+    ></ElTableColumn>
+    <ElTableColumn
+      prop="timeToTake"
+      label="预期耗时"
+      align="center"
+      width="100"
+    ></ElTableColumn>
+    <ElTableColumn
+      prop="startTime"
+      label="开始时间"
+      align="center"
+    ></ElTableColumn>
+    <ElTableColumn
+      prop="endTime"
+      label="结束时间"
+      align="center"
+    ></ElTableColumn>
+    <ElTableColumn
+      prop="state"
+      label="执行状态"
+      align="center"
+      width="120"
+      :filters="[{ text: '已创建', value: '已创建' }, { text: '进行中', value: '进行中' }, { text: '已完成', value: '已完成' }]"
+      :filter-method="(value, row) => {
+        return row.state === value;
+      }"
+    >
+      <template #default="scope">
+        <StateTag :state="scope.row.state" />
+      </template>
+    </ElTableColumn>
+  </ElTable>
+</template>
+
+<script lang="ts">
+import { defineComponent, PropType, ref } from 'vue';
+import { useStore } from '@/store';
+
+import StateTag from '@/components/StateTag.vue';
+import PriorityTag from '@/components/PriorityTag.vue';
+import { ElTable } from 'element-plus';
+
+interface Record {
+  commits: Array<CommitRecord>;
+  description: string;
+  endTime: string;
+  id: number;
+  priority: RecordPriority;
+  processorId: number;
+  processorName: string;
+  startTime: string;
+  state: RecordState;
+  timeToTake: 0;
+  title: string;
+}
+
+export default defineComponent({
+  name: 'GeneralTable',
+  components: { StateTag, PriorityTag },
+  props: {
+    data: Array as PropType<Record[]>,
+    showAdd: {
+      type: Boolean,
+      default: false,
+    },
+  },
+  emits: ['row-click', 'add'],
+  setup(props, { emit }) {
+    const store = useStore();
+    const rowClick = (...args: any[]) => {
+      emit('row-click', ...args);
+    };
+
+    const tableRef = ref<typeof ElTable|null>(null);
+
+    const clearFilter = () => {
+      console.log(tableRef);
+      tableRef.value!.clearFilter();
+    };
+
+    const add = () => {
+      emit('add');
+    };
+
+    return {
+      me: store.state.user.username,
+      rowClick,
+      tableRef,
+      clearFilter,
+      add,
+    };
+  },
+});
+</script>
+
+<style lang="scss" scoped>
+.table-prefix {
+  width: 90%;
+  margin: 0 auto;
+  & .right {
+    float: right;
+    & .prefix-right-button {
+      margin-left: 20px;
+    }
+  }
+}
+</style>

+ 21 - 0
src/components/PriorityTag.vue

@@ -0,0 +1,21 @@
+<template>
+  <template v-if="priority === '紧急'">
+    <ElTag type="warning">{{priority}}</ElTag>
+  </template>
+  <template v-else-if="priority === '一般'">
+    <ElTag>{{priority}}</ElTag>
+  </template>
+  <template v-else-if="priority === '宽松'">
+    <ElTag type="success">{{priority}}</ElTag>
+  </template>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+
+export default defineComponent({
+  props: {
+    priority: String,
+  },
+});
+</script>

+ 21 - 0
src/components/StateTag.vue

@@ -0,0 +1,21 @@
+<template>
+  <template v-if="state === '已创建'">
+    <ElTag type="primary">{{state}}</ElTag>
+  </template>
+  <template v-if="state === '进行中'">
+    <ElTag type="success">{{state}}</ElTag>
+  </template>
+  <template v-if="state === '已完成'">
+    <ElTag type="info">{{state}}</ElTag>
+  </template>
+</template>
+
+<script lang="ts">
+import { defineComponent } from 'vue';
+
+export default defineComponent({
+  props: {
+    state: String,
+  },
+});
+</script>

+ 8 - 2
src/store/modules/workspace.ts

@@ -3,11 +3,12 @@ import {
   ActionTree, GetterTree, Module, MutationTree,
 } from 'vuex';
 import {
-  WorkspaceState, RootState, ChangeProjectPayload,
+  WorkspaceState, RootState, ChangeProjectPayload, UserState,
 } from '../typings';
 
 const DEFAULT: WorkspaceState = {
   currentProjectId: -1,
+  currentProjectMembers: [],
   projectList: [],
 };
 
@@ -16,7 +17,9 @@ const actions: ActionTree<WorkspaceState, RootState> = {
     const projectList = await ProjectAPI.getProjectListByUserId(userId);
     commit('updateProjectList', projectList);
   },
-  changeProject({ commit }, payload: ChangeProjectPayload) {
+  async changeProject({ commit }, payload: ChangeProjectPayload) {
+    const members = await ProjectAPI.getProjectMembersByProjectId(payload.projectId);
+    commit('updateMembers', members);
     commit('changeProject', payload);
   },
   resetCurrentProject({ commit }) {
@@ -25,6 +28,9 @@ const actions: ActionTree<WorkspaceState, RootState> = {
 };
 
 const mutations: MutationTree<WorkspaceState> = {
+  updateMembers(state, payload: Array<UserState>) {
+    state.currentProjectMembers = payload;
+  },
   updateProjectList(state, payload: Array<Project>) {
     state.projectList = payload;
   },

+ 1 - 0
src/store/typings.ts

@@ -21,6 +21,7 @@ export interface UserState {
 
 export interface WorkspaceState {
   currentProjectId: ProjectId;
+  currentProjectMembers: Array<UserState>;
   projectList: Array<Project>;
 }
 

+ 11 - 1
src/typings/project.d.ts

@@ -6,7 +6,9 @@ declare interface Project {
 }
 
 declare type TreeNodeType = '需求' | '任务';
-declare type TreeNodeState = '已创建' | '进行中' | '已结束';
+
+declare type RecordPriority = '宽松' | '紧急' | '一般';
+declare type RecordState = '已创建' | '进行中' | '已结束';
 
 declare interface TreeNode {
   deep?: number;
@@ -26,4 +28,12 @@ declare interface TreeNode {
   userId?: number;
 }
 
+declare interface CommitRecord {
+  gitlabUsername: string;
+  id: string;
+  message: string;
+  timestamp: string;
+  title: string;
+}
+
 declare type Role = 'TEACHER' | 'STUDENT' | 'ADMIN' | 'GUEST';

+ 2 - 1
src/utils/axios.ts

@@ -30,8 +30,9 @@ _axios.interceptors.request.use(
 // Add a response interceptor
 _axios.interceptors.response.use(
   (response) => {
-    if (response.data.code === 10110) {
+    if (response.status === 403) {
       destroyToken();
+      window.location.href = '/';
       return;
     }
     return response.data.data;

+ 11 - 92
src/views/Project/BugList.vue

@@ -1,106 +1,21 @@
 <template>
 <div class="table-container">
-  <ElTable
+  <GeneralTable
     :data="bugList"
-    :default-sort="{prop: 'id', order: 'descending'}"
+    :showAdd="true"
+    @add="addBugRecord"
     @row-click="openDrawer"
-  >
-    <ElTableColumn
-      prop="id"
-      label="ID"
-      align="center"
-      width="60"
-    ></ElTableColumn>
-    <ElTableColumn
-      label="优先级"
-      align="center"
-      width="80"
-      :filters="[{ text: '紧急', value: '紧急' }, { text: '一般', value: '一般' }, { text: '宽松', value: '宽松' }]"
-      :filter-method="(value, row) => {
-        return row.priority === value;
-      }"
-      filter-placement="bottom-end"
-    >
-      <template #default="scope">
-        <template v-if="scope.row.priority === '紧急'">
-          <ElTag type="warning">{{scope.row.priority}}</ElTag>
-        </template>
-        <template v-else-if="scope.row.priority === '一般'">
-          <ElTag>{{scope.row.priority}}</ElTag>
-        </template>
-        <template v-else-if="scope.row.priority === '宽松'">
-          <ElTag type="success">{{scope.row.priority}}</ElTag>
-        </template>
-      </template>
-    </ElTableColumn>
-    <ElTableColumn
-      prop="title"
-      label="名称"
-      align="center"
-      width="180"
-    ></ElTableColumn>
-    <ElTableColumn
-      prop="processorName"
-      label="处理人"
-      align="center"
-      width="100"
-    ></ElTableColumn>
-    <ElTableColumn
-      prop="expectedTimeCost"
-      label="预期耗时"
-      align="center"
-      width="100"
-    ></ElTableColumn>
-    <ElTableColumn
-      prop="startTime"
-      label="开始时间"
-      align="center"
-      width="180"
-    ></ElTableColumn>
-    <ElTableColumn
-      prop="endTime"
-      label="结束时间"
-      align="center"
-      width="180"
-    ></ElTableColumn>
-    <ElTableColumn
-      prop="state"
-      label="执行状态"
-      align="center"
-      width="100"
-    >
-      <template #default="scope">
-        <template v-if="scope.row.state === 'CREATED'">
-          <ElTag type="primary">已创建</ElTag>
-        </template>
-        <template v-if="scope.row.state === 'PROCESSING'">
-          <ElTag type="success">进行中</ElTag>
-        </template>
-        <template v-if="scope.row.state === 'FINISHED'">
-          <ElTag type="info">已完成</ElTag>
-        </template>
-      </template>
-    </ElTableColumn>
-    <ElTableColumn
-      label="详情"
-      align="center"
-    >
-      <template #default="scope">
-        <ElButton
-          type="text"
-          size="small"
-          @click="openDrawer(scope.row)"
-        >编辑</ElButton>
-      </template>
-    </ElTableColumn>
-  </ElTable>
+  />
 </div>
 </template>
 
 <script>
 import { BugListAPI } from '@/api';
 
+import GeneralTable from '@/components/GeneralTable.vue';
+
 export default {
+  components: { GeneralTable },
   data() {
     return {
       bugList: [],
@@ -114,8 +29,12 @@ export default {
       this.bugList = await BugListAPI.getBugListByProjectId(this.$store.state.workspace.currentProjectId);
     },
     openDrawer(row) {
+      // 打开详情抽屉
       console.log(row);
     },
+    addBugRecord() {
+      // TODO: 添加一条bug记录
+    },
   },
 };
 </script>

+ 6 - 92
src/views/Project/TaskList.vue

@@ -1,106 +1,19 @@
 <template>
 <div class="table-container">
-  <ElTable
+  <GeneralTable
     :data="taskList"
-    :default-sort="{prop: 'id', order: 'descending'}"
     @row-click="openDrawer"
-  >
-    <ElTableColumn
-      prop="id"
-      label="ID"
-      align="center"
-      width="60"
-    ></ElTableColumn>
-    <ElTableColumn
-      label="优先级"
-      align="center"
-      width="80"
-      :filters="[{ text: '紧急', value: '紧急' }, { text: '一般', value: '一般' }, { text: '宽松', value: '宽松' }]"
-      :filter-method="(value, row) => {
-        return row.priority === value;
-      }"
-      filter-placement="bottom-end"
-    >
-      <template #default="scope">
-        <template v-if="scope.row.priority === '紧急'">
-          <ElTag type="warning">{{scope.row.priority}}</ElTag>
-        </template>
-        <template v-else-if="scope.row.priority === '一般'">
-          <ElTag>{{scope.row.priority}}</ElTag>
-        </template>
-        <template v-else-if="scope.row.priority === '宽松'">
-          <ElTag type="success">{{scope.row.priority}}</ElTag>
-        </template>
-      </template>
-    </ElTableColumn>
-    <ElTableColumn
-      prop="title"
-      label="名称"
-      align="center"
-      width="180"
-    ></ElTableColumn>
-    <ElTableColumn
-      prop="processorName"
-      label="处理人"
-      align="center"
-      width="100"
-    ></ElTableColumn>
-    <ElTableColumn
-      prop="timeToTake"
-      label="预期耗时"
-      align="center"
-      width="100"
-    ></ElTableColumn>
-    <ElTableColumn
-      prop="startTime"
-      label="开始时间"
-      align="center"
-      width="180"
-    ></ElTableColumn>
-    <ElTableColumn
-      prop="endTime"
-      label="结束时间"
-      align="center"
-      width="180"
-    ></ElTableColumn>
-    <ElTableColumn
-      prop="state"
-      label="执行状态"
-      align="center"
-      width="100"
-    >
-      <template #default="scope">
-        <template v-if="scope.row.state === 'CREATED'||scope.row.state === '已创建'">
-          <ElTag type="primary">已创建</ElTag>
-        </template>
-        <template v-if="scope.row.state === 'PROCESSING'||scope.row.state === '进行中'">
-          <ElTag type="success">进行中</ElTag>
-        </template>
-        <template v-if="scope.row.state === 'FINISHED'||scope.row.state === '已完成'">
-          <ElTag type="info">已完成</ElTag>
-        </template>
-      </template>
-    </ElTableColumn>
-    <ElTableColumn
-      label="详情"
-      align="center"
-    >
-      <template #default="scope">
-        <ElButton
-          type="text"
-          size="small"
-          @click="openDrawer(scope.row)"
-        >编辑</ElButton>
-      </template>
-    </ElTableColumn>
-  </ElTable>
+  />
 </div>
 </template>
 
 <script>
 import { RequirementAPI } from '@/api';
 
+import GeneralTable from '@/components/GeneralTable.vue';
+
 export default {
+  components: { GeneralTable },
   data() {
     return {
       taskList: [],
@@ -114,6 +27,7 @@ export default {
       this.taskList = await RequirementAPI.getTasksByProjectId(this.$store.state.workspace.currentProjectId);
     },
     openDrawer(row) {
+      // TODO: 打开详情抽屉
       console.log(row);
     },
   },