浏览代码

Item grouping function / 项目分组功能

star7th 4 年之前
父节点
当前提交
a04df7e4ce

+ 24 - 5
server/Application/Api/Controller/ItemController.class.php

@@ -141,7 +141,10 @@ class ItemController extends BaseController {
     //我的项目列表
     public function myList(){
         $login_user = $this->checkLogin();  
-        $original = I("original/d") ? I("original/d") : 0; //1:只返回自己原创的项目;默认是0      
+        $original = I("original/d") ? I("original/d") : 0; //1:只返回自己原创的项目;默认是0 
+        $item_group_id = I("item_group_id/d") ? I("item_group_id/d") : 0; //项目分组id。默认是0
+
+        $where = "uid = '$login_user[uid]' " ;     
         $member_item_ids = array(-1) ; 
         $item_members = D("ItemMember")->where("uid = '$login_user[uid]'")->select();
         if ($item_members) {
@@ -155,7 +158,15 @@ class ItemController extends BaseController {
                 $member_item_ids[] = $value['item_id'] ;
             }
         }
-        $items  = D("Item")->field("item_id,uid,item_name,item_domain,item_type,last_update_time,item_description,is_del,password")->where("uid = '$login_user[uid]' or item_id in ( ".implode(",", $member_item_ids)." ) ")->order("item_id asc")->select();
+
+        $where .= " or item_id in ( ".implode(",", $member_item_ids)." ) ";
+        if($item_group_id){
+            $res = D("ItemGroup")->where(" id = '$item_group_id' ")->find();
+            if($res){
+                $where = " ({$where}) and item_id in ({$res['item_ids']}) ";
+            }
+        }
+        $items  = D("Item")->field("item_id,uid,item_name,item_domain,item_type,last_update_time,item_description,is_del,password")->where($where)->order("item_id asc")->select();
         
         
         foreach ($items as $key => $value) {
@@ -203,7 +214,7 @@ class ItemController extends BaseController {
         }
 
         //读取项目顺序
-        $item_sort = D("ItemSort")->where("uid = '$login_user[uid]'")->find();
+        $item_sort = D("ItemSort")->where("uid = '$login_user[uid]'  and item_group_id = '$item_group_id' ")->find();
         if ($item_sort) {
             $item_sort_data = json_decode(htmlspecialchars_decode($item_sort['item_sort_data']) , true) ;
             //var_dump($item_sort_data);
@@ -592,8 +603,16 @@ class ItemController extends BaseController {
         $login_user = $this->checkLogin();
 
         $data = I("data");
-        D("ItemSort")->where(" uid = '$login_user[uid]' ")->delete();
-        $ret = D("ItemSort")->add(array("item_sort_data"=>$data,"uid"=>$login_user['uid'],"addtime"=>time()),array(),true);
+        $item_group_id = I("item_group_id/d");
+
+        $res = D("ItemSort")->where("  uid ='{$login_user[uid]}' and item_group_id = $item_group_id ")->find() ;
+        if($res){
+            $ret = D("ItemSort")->where("  uid ='{$login_user[uid]}' and item_group_id = $item_group_id ")->save(array("item_sort_data"=>$data,"addtime"=>time()));
+        }else{
+            $ret = D("ItemSort")->add(array("item_sort_data"=>$data,"item_group_id"=>$item_group_id,"uid"=>$login_user['uid'],"addtime"=>time()));
+
+        }
+
 
         if ($ret) {
             $this->sendResult(array());

+ 90 - 0
server/Application/Api/Controller/ItemGroupController.class.php

@@ -0,0 +1,90 @@
+<?php
+namespace Api\Controller;
+use Think\Controller;
+/*
+    项目分组管理
+ */
+class ItemGroupController extends BaseController {
+
+    //添加和编辑
+    public function save(){
+        $login_user = $this->checkLogin();
+
+        $group_name = I("group_name");
+        $item_ids = I("item_ids");
+        $id = I("id/d");
+
+        if ($id) {
+            
+            D("ItemGroup")->where(" id = '$id' ")->save(array("group_name"=>$group_name,"item_ids"=>$item_ids));
+
+        }else{
+            $data = array() ;
+            $data['uid'] = $login_user['uid'] ;
+            $data['group_name'] = $group_name ;
+            $data['item_ids'] = $item_ids ;
+            $data['created_at'] = date("Y-m-d H:i:s") ;
+            $data['updated_at'] = date("Y-m-d H:i:s") ;
+            $id = D("ItemGroup")->add($data);  
+        }
+
+        usleep(200000);
+        $return = D("ItemGroup")->where(" id = '$id' ")->find();
+
+        if (!$return && !$id ) {
+            $return = array() ;
+            $return['error_code'] = 10103 ;
+            $return['error_message'] = 'request  fail' ;
+        }
+
+        $this->sendResult($return);
+        
+    }
+
+    //获取列表
+    public function getList(){
+        $login_user = $this->checkLogin();
+        if ($login_user['uid'] > 0 ) {
+            $ret = D("ItemGroup")->where(" uid = '$login_user[uid]' ")->order(" s_number asc,id asc  ")->select();
+        }
+        if ($ret) {
+           $this->sendResult($ret);
+        }else{
+            $this->sendResult(array());
+        }
+    }
+
+    //删除
+    public function delete(){
+        $id = I("id/d")? I("id/d") : 0;
+        $login_user = $this->checkLogin();
+        if ($id && $login_user['uid']) {
+            $ret = D("ItemGroup")->where(" id = '$id' and uid = '$login_user[uid]'")->delete();
+        }
+        if ($ret) {
+            D("ItemGroup")->where(" id = '$id' ")->delete();
+            D("ItemSort")->where(" item_group_id = '$id' ")->delete();
+           $this->sendResult($ret);
+        }else{
+           $this->sendError(10101);
+        }
+    }
+
+    // 给我的项目组们保存顺序
+    public function saveSort(){
+        $login_user = $this->checkLogin();
+        $groups = I("groups") ;
+        $data_array = json_decode(htmlspecialchars_decode($groups) , true) ;
+        $uid = $login_user['uid'] ;
+        if($data_array){
+            foreach ($data_array as $key => $value) {
+                $id = $value['id'] ;
+                $ret = D("ItemGroup")->where(" id = '$id' and uid = '{$uid}'")->save(array('s_number'=>$value['s_number']));
+            }
+        }
+        $this->sendResult(array());
+
+    }
+
+
+}

+ 20 - 2
server/Application/Api/Controller/UpdateController.class.php

@@ -5,7 +5,7 @@ class UpdateController extends BaseController {
 
     //检测数据库并更新
     public function checkDb($showBack = true){
-        $version_num = 8 ;
+        $version_num = 9 ;
         $db_version_num = D("Options")->get("db_version_num");
         if(!$db_version_num || $db_version_num < $version_num ){
             $r = $this->updateSqlite();
@@ -395,7 +395,25 @@ class UpdateController extends BaseController {
             $sql = "ALTER TABLE ".C('DB_PREFIX')."runapi_flow_page ADD enabled int(1) NOT NULL DEFAULT '1' ;";
             D("mock")->execute($sql);
         }
-        
+
+         //给item_sort表增加item_group_id字段
+         if (!$this->_is_column_exist("item_sort","item_group_id")) {
+            $sql = "ALTER TABLE ".C('DB_PREFIX')."item_sort ADD item_group_id int(10) NOT NULL DEFAULT '0' ;";
+            D("mock")->execute($sql);
+        }
+
+        //创建item_group表
+        $sql = "CREATE TABLE IF NOT EXISTS `item_group` (
+            `id`  INTEGER PRIMARY KEY ,
+            `uid` int(11) NOT NULL DEFAULT '0',
+            `group_name` CHAR(2000) NOT NULL DEFAULT '',
+            `item_ids` text NOT NULL DEFAULT '',
+            `s_number` int(11) NOT NULL DEFAULT '0',
+            `created_at` CHAR(2000) NOT NULL DEFAULT '',
+            `updated_at` CHAR(2000) NOT NULL DEFAULT ''
+            )";
+        D("User")->execute($sql);
+
         //留个注释提醒自己,如果更新数据库结构,务必更改上面的$version_num
         //留个注释提醒自己,如果更新数据库结构,务必更改上面的$version_num
         //留个注释提醒自己,如果更新数据库结构,务必更改上面的$version_num

+ 71 - 0
web_src/src/components/common/ElTableDraggable.vue

@@ -0,0 +1,71 @@
+<template>
+  <div ref="wrapper">
+    <div :key="tableKey">
+      <slot></slot>
+    </div>
+  </div>
+</template>
+
+<script>
+import sortable from 'sortablejs'
+export default {
+  name: 'ElementUiElTableDraggable',
+  props: {
+    handle: {
+      type: String,
+      default: ''
+    },
+    animate: {
+      type: Number,
+      default: 100
+    }
+  },
+  data() {
+    return {
+      tableKey: 0
+    }
+  },
+  methods: {
+    makeTableSortAble() {
+      const table = this.$children[0].$el.querySelector(
+        '.el-table__body-wrapper tbody'
+      )
+      sortable.create(table, {
+        handle: this.handle,
+        animation: this.animate,
+        onStart: () => {
+          this.$emit('drag')
+        },
+        onEnd: ({ newIndex, oldIndex }) => {
+          this.keepWrapperHeight(true)
+          this.tableKey = Math.random()
+          const arr = this.$children[0].data
+          const targetRow = arr.splice(oldIndex, 1)[0]
+          arr.splice(newIndex, 0, targetRow)
+          this.$emit('drop', { targetObject: targetRow, list: arr })
+        }
+      })
+    },
+    keepWrapperHeight(keep) {
+      // eslint-disable-next-line prefer-destructuring
+      const wrapper = this.$refs.wrapper
+      if (keep) {
+        wrapper.style.minHeight = `${wrapper.clientHeight}px`
+      } else {
+        wrapper.style.minHeight = 'auto'
+      }
+    }
+  },
+  mounted() {
+    this.makeTableSortAble()
+  },
+  watch: {
+    tableKey() {
+      this.$nextTick(() => {
+        this.makeTableSortAble()
+        this.keepWrapperHeight(false)
+      })
+    }
+  }
+}
+</script>

+ 81 - 9
web_src/src/components/item/Index.vue

@@ -123,6 +123,33 @@
           :keyword="keyword"
           :itemList="itemList"
         ></Search>
+        <div
+          class="group-bar"
+          v-show="!showSearch && (itemGroupId > 0 || itemList.length > 5)"
+        >
+          <el-radio-group
+            v-model="itemGroupId"
+            @change="changeGroup"
+            size="small"
+          >
+            <el-radio-button class="radio-button" label="0">{{
+              $t('all_items')
+            }}</el-radio-button>
+            <el-radio-button
+              v-for="element in itemGroupList"
+              :key="element.id"
+              class="radio-button"
+              :label="element.id"
+              >{{ element.group_name }}</el-radio-button
+            >
+          </el-radio-group>
+          <router-link class="group-link" to="/item/group/index">
+            {{ $t('manage_item_group') }}
+          </router-link>
+          <router-link class="group-link" to="/item/add">
+            {{ $t('new_item') }}
+          </router-link>
+        </div>
         <ul class="thumbnails" id="item-list" v-if="!showSearch">
           <draggable
             v-model="itemList"
@@ -134,11 +161,7 @@
             <li
               class="text-center"
               v-for="item in itemList"
-              v-dragging="{
-                item: item,
-                list: itemList,
-                group: 'item'
-              }"
+              v-loading="loading"
               :key="item.item_id"
             >
               <router-link
@@ -326,6 +349,22 @@ a {
   color: #ffffff;
   background-color: #ffffff;
 }
+.group-bar {
+  margin-top: 20px;
+  margin-bottom: 20px;
+  margin-left: 60px;
+}
+.group-link {
+  margin-left: 10px;
+  font-size: 12px;
+}
+</style>
+
+<style>
+.group-bar .el-radio-button__inner {
+  border-radius: 30px !important;
+  margin-right: 5px;
+}
 </style>
 
 <script>
@@ -347,7 +386,10 @@ export default {
       keyword: '',
       lang: '',
       username: '',
-      showSearch: false
+      showSearch: false,
+      itemGroupId: '0',
+      itemGroupList: [],
+      loading: false
     }
   },
   watch: {
@@ -372,7 +414,12 @@ export default {
   },
   methods: {
     get_item_list() {
-      this.request('/api/item/myList', {}).then(data => {
+      this.loading = true
+      const itemGroupId = this.itemGroupId
+      this.request('/api/item/myList', {
+        item_group_id: itemGroupId
+      }).then(data => {
+        this.loading = false
         this.itemList = data.data
       })
     },
@@ -496,6 +543,7 @@ export default {
       var url = DocConfig.server + '/api/item/sort'
       var params = new URLSearchParams()
       params.append('data', JSON.stringify(data))
+      params.append('item_group_id', this.itemGroupId)
       that.axios.post(url, params).then(function(response) {
         if (response.data.error_code === 0) {
           that.get_item_list()
@@ -524,14 +572,38 @@ export default {
     checkDb() {
       var url = DocConfig.server + '/api/update/checkDb'
       this.axios.get(url)
+    },
+    getItemGroupList() {
+      this.request('/api/itemgroup/getList', {}).then(data => {
+        this.itemGroupList = data.data
+        const deaultItemGroupId = localStorage.getItem('deaultItemGroupId')
+        // 循环欧判断记住的id是否还存在列表中
+        this.itemGroupList.map(element => {
+          if (element.id == deaultItemGroupId) {
+            this.itemGroupId = deaultItemGroupId
+          }
+        })
+        this.get_item_list()
+      })
+    },
+    changeGroup() {
+      localStorage.setItem('deaultItemGroupId', this.itemGroupId)
+      this.get_item_list()
     }
   },
   mounted() {
-    this.get_item_list()
+    this.checkDb()
     this.user_info()
     this.lang = DocConfig.lang
     this.script_cron()
-    this.checkDb()
+    const deaultItemGroupId = localStorage.getItem('deaultItemGroupId')
+    if (deaultItemGroupId === null) {
+      this.get_item_list()
+      this.itemGroupId = '0'
+    } else {
+      this.itemGroupId = deaultItemGroupId
+    }
+    this.getItemGroupList()
     this.get_user_info(response => {
       if (response.data.error_code === 0) {
         this.username = response.data.data.username

+ 251 - 0
web_src/src/components/item/group/Index.vue

@@ -0,0 +1,251 @@
+<template>
+  <div class="hello">
+    <Header></Header>
+
+    <el-container>
+      <el-card class="center-card">
+        <el-button type="text" class="goback-btn" @click="goback"
+          ><i class="el-icon-back"></i>&nbsp;{{ $t('goback') }}</el-button
+        >
+        <el-button type="text" class="add-cat" @click="addDialog"
+          ><i class="el-icon-plus"></i>&nbsp;{{ $t('add_group') }}</el-button
+        >
+        <p class="tips" v-show="list && list.length > 1">
+          {{ $t('draggable_tips') }}
+        </p>
+        <el-table-draggable animate="200" @drop="onDropEnd">
+          <el-table
+            :show-header="false"
+            align="left"
+            :empty-text="$t('item_group_empty_tips')"
+            :data="list"
+            height="400"
+            style="width: 100%"
+          >
+            <el-table-column
+              prop="group_name"
+              :label="$t('group_name')"
+            ></el-table-column>
+
+            <el-table-column width="120" prop :label="$t('operation')">
+              <template slot-scope="scope">
+                <el-button @click="edit(scope.row)" type="text" size="small">{{
+                  $t('edit')
+                }}</el-button>
+
+                <el-button
+                  @click="del(scope.row.id)"
+                  type="text"
+                  size="small"
+                  >{{ $t('delete') }}</el-button
+                >
+              </template>
+            </el-table-column>
+          </el-table>
+        </el-table-draggable>
+      </el-card>
+      <el-dialog
+        :visible.sync="dialogFormVisible"
+        v-if="dialogFormVisible"
+        width="500px"
+        :close-on-click-modal="false"
+      >
+        <el-form>
+          <el-form-item>
+            <el-input v-model="MyForm.group_name" placeholder="组名"></el-input>
+          </el-form-item>
+        </el-form>
+        <div>
+          <p>{{ $t('select_item') }}</p>
+          <el-table
+            :show-header="false"
+            ref="multipleTable"
+            :data="itemList"
+            tooltip-effect="dark"
+            style="width: 100%"
+            @selection-change="handleSelectionChange"
+          >
+            <el-table-column type="selection" width="55"> </el-table-column>
+            <el-table-column prop="item_name" :label="$t('item_name')">
+            </el-table-column>
+          </el-table>
+        </div>
+
+        <div slot="footer" class="dialog-footer">
+          <el-button @click="dialogFormVisible = false">{{
+            $t('cancel')
+          }}</el-button>
+          <el-button type="primary" @click="MyFormSubmit">{{
+            $t('confirm')
+          }}</el-button>
+        </div>
+      </el-dialog>
+    </el-container>
+
+    <Footer></Footer>
+  </div>
+</template>
+
+<script>
+import ElTableDraggable from '@/components/common/ElTableDraggable'
+export default {
+  components: { ElTableDraggable },
+  data() {
+    return {
+      MyForm: {
+        id: '',
+        group_name: ''
+      },
+      list: [],
+      dialogFormVisible: false,
+      itemList: [],
+      multipleSelection: []
+    }
+  },
+  methods: {
+    geList() {
+      this.request('/api/itemgroup/getList', {}).then(data => {
+        this.list = data.data
+      })
+    },
+    MyFormSubmit() {
+      const group_name = this.MyForm.group_name
+      const id = this.MyForm.id
+      const item_ids_array = []
+      this.multipleSelection.map(element => {
+        item_ids_array.push(element.item_id)
+      })
+      const item_ids = item_ids_array.join(',')
+      this.request('/api/itemgroup/save', { group_name, id, item_ids }).then(
+        data => {
+          this.geList()
+          this.dialogFormVisible = false
+          this.multipleSelection = []
+          this.$nextTick(() => {
+            this.toggleSelection(this.multipleSelection)
+          })
+          this.MyForm.id = ''
+          this.MyForm.group_name = ''
+        }
+      )
+    },
+    edit(row) {
+      this.MyForm.id = row.id
+      this.MyForm.group_name = row.group_name
+      this.multipleSelection = []
+      const item_ids_array = row.item_ids.split(',')
+      item_ids_array.map(item_id => {
+        this.itemList.map(element2 => {
+          if (item_id == element2.item_id) {
+            this.multipleSelection.push(element2)
+          }
+        })
+      })
+      this.dialogFormVisible = true
+      this.$nextTick(() => {
+        this.toggleSelection(this.multipleSelection)
+      })
+    },
+
+    del(id) {
+      var that = this
+
+      this.$confirm(that.$t('confirm_delete'), ' ', {
+        confirmButtonText: that.$t('confirm'),
+        cancelButtonText: that.$t('cancel'),
+        type: 'warning'
+      }).then(() => {
+        this.request('/api/itemgroup/delete', { id }).then(data => {
+          this.geList()
+        })
+      })
+    },
+    addDialog() {
+      this.MyForm = {
+        id: '',
+        team_name: ''
+      }
+      this.dialogFormVisible = true
+    },
+    goback() {
+      this.$router.push({ path: '/item/index' })
+    },
+    get_item_list() {
+      this.request('/api/item/myList', {}).then(data => {
+        this.itemList = data.data
+      })
+    },
+    handleSelectionChange(val) {
+      this.multipleSelection = val
+    },
+    toggleSelection(rows) {
+      if (rows) {
+        rows.forEach(row => {
+          this.$refs.multipleTable.toggleRowSelection(row)
+        })
+      } else {
+        this.$refs.multipleTable.clearSelection()
+      }
+    },
+    onDropEnd() {
+      const groups_array = []
+      this.list.map((element, index) => {
+        groups_array.push({
+          id: element.id,
+          s_number: index + 1
+        })
+      })
+      this.request('/api/itemgroup/saveSort', {
+        groups: JSON.stringify(groups_array)
+      }).then(data => {
+        this.itemList = data.data
+      })
+    }
+  },
+
+  mounted() {
+    this.geList()
+    this.get_item_list()
+  },
+  beforeDestroy() {}
+}
+</script>
+
+<!-- Add "scoped" attribute to limit CSS to this component only -->
+<style scoped>
+.hello {
+  text-align: left;
+}
+
+.add-cat {
+  float: right;
+  margin-right: 15px;
+  font-size: 14px;
+}
+
+.center-card {
+  text-align: left;
+  width: 750px;
+  height: 600px;
+}
+
+.goback-btn {
+  z-index: 999;
+  font-size: 14px;
+}
+.tips {
+  color: #777;
+}
+</style>
+
+<!-- 全局css -->
+<style>
+.el-table .success-row {
+  background: #f0f9eb;
+}
+.el-table__empty-text {
+  text-align: left;
+  line-height: 30px !important;
+  margin-top: 20px;
+}
+</style>

+ 6 - 0
web_src/src/router/index.js

@@ -22,6 +22,7 @@ import Team from '@/components/team/Index'
 import TeamMember from '@/components/team/Member'
 import TeamItem from '@/components/team/Item'
 import Attachment from '@/components/attachment/Index'
+import ItemGroup from '@/components/item/group/Index'
 
 Vue.use(Router)
 
@@ -141,6 +142,11 @@ export default new Router({
       path: '/attachment/index',
       name: 'Attachment',
       component: Attachment
+    },
+    {
+      path: '/item/group/index',
+      name: 'ItemGroup',
+      component: ItemGroup
     }
   ]
 })

+ 10 - 2
web_src/static/lang/en.js

@@ -427,6 +427,14 @@ exports.default = {
   qcloud: 'qcloud',
   binding_item: 'Binding item',
   addtime: 'addtime',
-  system_update: 'System update'
+  system_update: 'System update',
 
-}
+  group_name: 'group name',
+  add_group: 'New group',
+  all_items: 'All items',
+  manage_item_group: 'Manage group',
+  draggable_tips: 'Press and hold to drag sort',
+  item_group_empty_tips: 'There is no grouping data. You can click the top right corner to create a new group',
+  select_item: 'Select item'
+
+}

+ 9 - 1
web_src/static/lang/zh-CN.js

@@ -417,5 +417,13 @@ exports.default = {
   qcloud: '腾讯云',
   binding_item: '绑定项目',
   addtime: '添加时间',
-  system_update: '系统更新'
+  system_update: '系统更新',
+
+  group_name: '组名',
+  add_group: '新建分组',
+  all_items: '所有项目',
+  manage_item_group: '项目分组管理',
+  draggable_tips: '按住可拖动排序',
+  item_group_empty_tips: '暂无分组数据。可点击右上角新建分组',
+  select_item: '选择项目'
 }