Bladeren bron

Merge branch 'dev'

* dev:
  修复更新部门负责人可能未生效
  优化组织结构页面
  refactor
  支持变更部门
  用 pinna 替换 vuex
  仅没有根组织时,允许批量导入
  用 pinna 替换 vuex
imndx 5 maanden geleden
bovenliggende
commit
626803eccd
25 gewijzigde bestanden met toevoegingen van 1093 en 541 verwijderingen
  1. 3 1
      organization-server/src/main/java/cn/wildfirechat/org/ServiceImpl.java
  2. 579 150
      organization-web/package-lock.json
  3. 13 11
      organization-web/package.json
  4. 8 0
      organization-web/src/api/api.js
  5. 1 1
      organization-web/src/api/axios.config.js
  6. 42 12
      organization-web/src/components/common/Home.vue
  7. 8 1
      organization-web/src/components/page/Login.vue
  8. 8 1
      organization-web/src/components/page/UpdatePwd.vue
  9. 12 5
      organization-web/src/components/page/organization/Department.vue
  10. 1 1
      organization-web/src/components/page/organization/ImportMember.vue
  11. 141 133
      organization-web/src/components/page/organization/Member.vue
  12. 8 1
      organization-web/src/components/page/organization/dialog/AddDepartmentMember.vue
  13. 35 16
      organization-web/src/components/page/organization/dialog/AddSubDepartment.vue
  14. 23 10
      organization-web/src/components/page/organization/dialog/ChooseDepartment.vue
  15. 12 9
      organization-web/src/components/page/organization/dialog/ChooseMember.vue
  16. 51 63
      organization-web/src/components/page/organization/dialog/UpdateDepartment.vue
  17. 9 1
      organization-web/src/components/page/organization/drawer/DeleteEmployee.vue
  18. 2 2
      organization-web/src/main.js
  19. 3 0
      organization-web/src/model/organization.js
  20. 1 1
      organization-web/src/router/index.js
  21. 0 93
      organization-web/src/store/components/org_store.js
  22. 0 29
      organization-web/src/store/components/user_store.js
  23. 8 0
      organization-web/src/store/pinia.js
  24. 96 0
      organization-web/src/store/stores/orgStore.js
  25. 29 0
      organization-web/src/store/stores/userStore.js

+ 3 - 1
organization-server/src/main/java/cn/wildfirechat/org/ServiceImpl.java

@@ -476,9 +476,11 @@ public class ServiceImpl implements Service {
         entity.createDt = optional.get().createDt;
         entity.updateDt = System.currentTimeMillis();
 
+        String orgManagerId = optional.get().managerId;
+
         organizationEntityRepository.save(entity);
 
-        if (!isEqual(entity.managerId, optional.get().managerId)) {
+        if (!isEqual(entity.managerId, orgManagerId)) {
             // 将新部门 manager 加入部门
             RelationshipID relationshipID = new RelationshipID();
             relationshipID.employeeId = entity.managerId;

File diff suppressed because it is too large
+ 579 - 150
organization-web/package-lock.json


+ 13 - 11
organization-web/package.json

@@ -9,33 +9,35 @@
     "lint": "vue-cli-service lint"
   },
   "dependencies": {
-    "axios": "^0.27.2",
+    "axios": "1.8.2",
     "core-js": "^3.6.5",
     "element-ui": "^2.15.8",
-    "vue": "^2.6.11",
+    "pinia": "^2.3.1",
+    "vue": "^2.7.14",
     "vue-i18n": "^8.23.0",
-    "vue-router": "^3.0.1",
-    "vuex": "^3.0.1"
+    "vue-router": "^3.0.1"
   },
   "devDependencies": {
     "@vue/cli-plugin-babel": "~4.5.4",
     "@vue/cli-plugin-eslint": "~4.5.4",
     "@vue/cli-service": "~4.5.4",
     "babel-eslint": "^10.1.0",
-    "eslint": "^6.7.2",
-    "eslint-plugin-vue": "^6.2.2",
-    "vue-template-compiler": "^2.6.11",
     "copy-dir": "^1.3.0",
     "cpy-cli": "^3.1.1",
     "del-cli": "^3.0.1",
-    "shelljs": "^0.7.6"
+    "eslint": "^6.7.2",
+    "eslint-plugin-vue": "^6.2.2",
+    "shelljs": "0.8.5",
+    "vue-template-compiler": "^2.7.14"
   },
   "eslintConfig": {
     "root": true,
     "env": {
       "node": true
     },
-    "ignorePatterns": ["**/vendor/**.js"],
+    "ignorePatterns": [
+      "**/vendor/**.js"
+    ],
     "extends": [
       "plugin:vue/essential",
       "eslint:recommended"
@@ -44,8 +46,8 @@
       "parser": "babel-eslint"
     },
     "rules": {
-        "no-unused-vars": "off",
-        "no-prototype-builtins": "off"
+      "no-unused-vars": "off",
+      "no-prototype-builtins": "off"
     }
   },
   "browserslist": [

+ 8 - 0
organization-web/src/api/api.js

@@ -17,6 +17,14 @@ export default {
     async createEmployee(employee) {
         return axios.post('/employee/create', employee);
     },
+    /**
+     * 员工变更部门
+     * @param employee
+     * @return {Promise<axios.AxiosResponse<any>>}
+     */
+    async moveEmployee(moveEmployeePojo) {
+        return axios.post('/employee/move', moveEmployeePojo);
+    },
     async queryEmployee(employeeId) {
         return axios.post('/employee/query', {employeeId});
     },

+ 1 - 1
organization-web/src/api/axios.config.js

@@ -3,7 +3,7 @@ import App from '../main'
 // axios实例
 const instance = Axios.create({
     // 针对实际情况进行修改
-    baseURL: './api',
+    baseURL: 'http://localhost:8880/api',
     withCredentials: true,
     headers: {
         'Content-Type': 'application/json;charset=utf-8',

+ 42 - 12
organization-web/src/components/common/Home.vue

@@ -4,8 +4,8 @@
             <div style="height: 60px; display: flex; justify-content: center;align-items: center" @click="go2home">
                 <p>野火组织架构管理后台</p>
             </div>
-            <el-menu default-active='/contact/departmentanduser' router>
-                <el-menu-item index="/contact/departmentanduser">成员与部门</el-menu-item>
+            <el-menu router>
+                <el-menu-item index="/organization/departmentanduser">成员与部门</el-menu-item>
             </el-menu>
         </el-aside>
         <el-container :class="{'content-collapse':collapse}">
@@ -53,8 +53,8 @@
 </template>
 
 <script>
-
-import {mapState} from "vuex";
+import {useUserStore} from "@/store/stores/userStore";
+import {useOrgStore} from "@/store/stores/orgStore";
 import Breadcrumb from "@/components/common/Breadcrumb";
 
 export default {
@@ -81,16 +81,32 @@ export default {
     components: {
         Breadcrumb
     },
+
+    setup() {
+        const userStore = useUserStore();
+        const orgStore = useOrgStore();
+        return {userStore, orgStore};
+    },
+
     created() {
-        this.$store.dispatch('getAccount')
+        this.userStore.getAccount();
+        this.orgStore.getRootOrganizationsWithChildren();
     },
-    computed: mapState({
-        account: state => state.user.account,
-    }),
+
+    computed: {
+        account() {
+            return this.userStore.account;
+        },
+        rootOrganizations() {
+            return this.orgStore.rootOrganizations;
+        },
+    },
+
     methods: {
         go2home() {
-            if (this.$router.history.current.path !== '/index') {
-                this.$router.replace('/index')
+            const defaultPath = this.rootOrganizations.length > 0 ? '/organization/departmentanduser' : '/organization//import-member';
+            if (this.$router.history.current.path !== defaultPath) {
+                this.$router.replace(defaultPath);
             }
         },
         logout() {
@@ -104,15 +120,29 @@ export default {
                     if (this.updatePwdRequest.newPwd !== this.updatePwdRequest.confirmNewPwd) {
                         this.$message.error('两次输入的密码不一致');
                     } else {
-                        this.$store.dispatch('updatePwd', {
+                        this.userStore.updatePwd({
                             oldPassword: this.updatePwdRequest.oldPwd,
                             newPassword: this.updatePwdRequest.newPwd
-                        })
+                        });
                         this.modifyPwdDialogVisible = false;
                     }
                 }
             });
         }
+    },
+    watch: {
+        rootOrganizations: {
+            handler(newVal) {
+                // 当根组织数据变化时,检查是否需要跳转到批量导入页面
+                if (newVal.length === 0 && this.$router.history.current.path !== '/organization/departmentanduser/import-member') {
+                    this.$router.push('/organization/departmentanduser/import-member')
+                } else if (newVal.length > 0 && this.$router.history.current.path !== '/organization/departmentanduser') {
+                    this.$router.push('/organization/departmentanduser')
+                }
+            },
+            deep: true,
+            immediate: true
+        }
     }
 }
 </script>

+ 8 - 1
organization-web/src/components/page/Login.vue

@@ -24,6 +24,7 @@
 </template>
 <script>
 import LoginRequest from "@/model/loginRequest";
+import { useUserStore } from "@/store/stores/userStore";
 
 export default {
     data: function () {
@@ -43,13 +44,19 @@ export default {
             },
         }
     },
+
+    setup() {
+        const userStore = useUserStore();
+        return { userStore };
+    },
+
     methods: {
         submitForm(formName) {
             let self = this;
             self.loading = true
             this.$refs[formName].validate((valid) => {
                 if (valid) {
-                    this.$store.dispatch('login', new LoginRequest(
+                    this.userStore.login(new LoginRequest(
                         this.loginForm.username, this.loginForm.password
                     )).then(() => {
                         console.log('login success');

+ 8 - 1
organization-web/src/components/page/UpdatePwd.vue

@@ -24,7 +24,14 @@
 </template>
 
 <script>
+import { useUserStore } from "@/store/stores/userStore";
+
 export default {
+    setup() {
+        const userStore = useUserStore();
+        return { userStore };
+    },
+
     data() {
         return {
             loading: false,
@@ -39,7 +46,7 @@ export default {
                 return
             }
             this.loading = true
-            this.$store.dispatch('updatePwd', this.user).then(() => {
+            this.userStore.updatePwd(this.user).then(() => {
                 self.$message(this.$t('common.action_success'))
             }).finally(() => {
                 self.loading = false

+ 12 - 5
organization-web/src/components/page/organization/Department.vue

@@ -49,7 +49,7 @@
 </template>
 
 <script>
-import {mapState} from "vuex";
+import { useOrgStore } from "@/store/stores/orgStore";
 
 export default {
     name: "createDepartment",
@@ -59,11 +59,18 @@ export default {
             input: '',
         }
     },
+
+    setup() {
+        const orgStore = useOrgStore();
+        return { orgStore };
+    },
+
     computed: {
-        ...mapState({
-            rootOrganizations: state => state.org.rootOrganizations,
-        })
+        rootOrganizations() {
+            return this.orgStore.rootOrganizations;
+        }
     },
+
     methods: {
         toggleSelection(rows) {
             if (rows) {
@@ -81,7 +88,7 @@ export default {
             console.log('to load data', tree, treeNode)
             let data = tree;
             if (!data._orgWithChildren && data.id) {
-                await this.$store.dispatch('queryOrganizationWithChildren', data)
+                await this.orgStore.queryOrganizationWithChildren(data)
                 this.currentOrg = data;
                 resolve(data.children);
             } else {

+ 1 - 1
organization-web/src/components/page/organization/ImportMember.vue

@@ -16,7 +16,7 @@
                     :action="uploadUrl"
                     :with-credentials="true"
                     accept=".xls,.xlsx"
-                    :show-file-list="false"
+                    :show-file-list="true"
                     :limit="1"
                     :on-change="handleChange"
                     :auto-upload="false"

+ 141 - 133
organization-web/src/components/page/organization/Member.vue

@@ -2,149 +2,155 @@
     <el-container style="height: 100%">
         <el-aside style="border-right: 1px solid #e6e6e6; padding-right: 20px">
             <el-input v-model="input" placeholder="请输入姓名、邮箱或手机号"></el-input>
-            <el-tree v-if="!input" :data="rootOrganizations"
-                     ref="tree"
-                     :expand-on-click-node="false"
-                     :props="defaultProps"
-                     :render-after-expand='false'
-                     lazy
-                     :load="loadNode"
-                     @node-click="handleNodeClick"
+            <el-tree v-if="!input" :data="rootOrganizations" ref="tree" :expand-on-click-node="true"
+                     :props="defaultProps" :render-after-expand='false' lazy :load="loadNode" @node-click="handleNodeClick"
                      @node-expand="handleNodeExpand">
                 <span class="custom-tree-node" slot-scope="{ node}">
                     <span>{{ node.label }}</span>
                     <el-dropdown trigger="click" size="medium" @command="handleDepartmentCommand">
-                         <span>
+                        <span>
                             <el-icon class="el-icon-more"/>
                         </span>
                         <el-dropdown-menu slot="dropdown">
-                            <el-dropdown-item v-if="!node.data.groupId" :command="{c:'create-org-group', node: node, depart:node.data}">创建组织官方群</el-dropdown-item>
-                            <el-dropdown-item :command="{c:'edit', node: node, depart:node.data}">编辑部门</el-dropdown-item>
-                            <el-dropdown-item :command="{c:'add-sub', node: node, depart:node.data}">添加子部门</el-dropdown-item>
-                            <el-dropdown-item style="color: red" :command="{c:'remove', node: node, depart:node.data}">删除部门</el-dropdown-item>
-                       </el-dropdown-menu>
+                            <el-dropdown-item v-if="!node.data.groupId"
+                                              :command="{ c: 'create-org-group', node: node, depart: node.data }">创建组织官方群</el-dropdown-item>
+                            <el-dropdown-item
+                                :command="{ c: 'edit', node: node, depart: node.data }">编辑部门</el-dropdown-item>
+                            <el-dropdown-item
+                                :command="{ c: 'add-sub', node: node, depart: node.data }">添加子部门</el-dropdown-item>
+                            <el-dropdown-item style="color: red"
+                                              :command="{ c: 'remove', node: node, depart: node.data }">删除部门</el-dropdown-item>
+                        </el-dropdown-menu>
                     </el-dropdown>
-              </span>
+                </span>
             </el-tree>
             <div v-else>
                 TODO search result
             </div>
         </el-aside>
-        <el-container>
+        <el-container v-if="currentOrg">
             <el-header>
-                <div style="height: 100%; display: flex; flex-direction: row; align-items: center; justify-content: center">
-                    <p style="flex: 1 1 auto"> {{ currentOrg && currentOrg.name }}</p>
-                    <el-button type="primary" icon="el-icon-plus" @click="showAddDepartmentMemberDialog = true">添加成员</el-button>
-                    <el-button v-if="rootOrganizations.length === 0" @click="importMember">批量导入</el-button>
-                    <el-button>变更部门</el-button>
-                    <el-button type="danger">操作离职</el-button>
+                <div
+                    style="display: flex; flex-direction: row; align-items: center; justify-content: center; height: 100%">
+                    <div v-if="currentOrg" style="flex: 1 1 auto">
+                        <p> {{ currentOrg.name }}</p>
+                        <p v-if="currentOrg.managerName" style="font-size: 12px"> {{
+                                "部门负责人: " + currentOrg.managerName
+                            }}</p>
+                    </div>
+                    <el-button type="primary" icon="el-icon-plus" v-if="rootOrganizations.length > 0"
+                               @click="showAddDepartmentMemberDialog = true">添加成员
+                    </el-button>
+                    <el-button type="primary" v-if="rootOrganizations.length === 0"
+                               @click="importMember">批量导入
+                    </el-button>
                 </div>
             </el-header>
-            <el-table
-                ref="multipleTable"
-                :data="currentOrgEmployees"
-                empty-text="当前公司或部门没有直属员工"
-                tooltip-effect="dark"
-                style="width: 100%"
-                :cell-style="{padding: '0', height: '50px'}"
-                @selection-change="handleSelectionChange">
-                <el-table-column
-                    type="selection"
-                    width="55">
+            <el-table ref="multipleTable" :data="currentOrgEmployees" empty-text="当前公司或部门没有直属员工" tooltip-effect="dark"
+                      type="default" style="width: 100%" :cell-style="{ padding: '0', height: '50px' }">
+                <el-table-column type="default" width="55">
                 </el-table-column>
-                <el-table-column
-                    prop="name"
-                    label="姓名"
-                    width="120">
-                    <!--                    <template slot-scope="scope">{{ scope.row.date }}</template>-->
+                <el-table-column prop="name" label="姓名" width="120">
                 </el-table-column>
-                <el-table-column
-                    prop="title"
-                    label="职位"
-                    width="120">
+                <el-table-column prop="title" label="职位" width="120">
                 </el-table-column>
-                <el-table-column
-                    prop="mobile"
-                    label="手机号"
-                    show-overflow-tooltip>
+                <el-table-column prop="mobile" label="手机号" show-overflow-tooltip>
                 </el-table-column>
                 <el-table-column>
                     <template v-slot="scope">
-                        <el-button class="f-btn" @click="handleClickEmployee(scope.row)" type="text" size="small">查看详情</el-button>
-                        <el-button class="f-btn" @click="handleClickEmployee(scope.row)" type="text" size="small">变更部门</el-button>
-                        <el-button class="f-btn" @click="handleClickEmployee(scope.row)" type="text" size="small">操作离职</el-button>
+                        <el-button v-if="false" class="f-btn" @click="handleClickEmployee(scope.row)" type="text"
+                                   size="small">查看详情
+                        </el-button>
+                        <el-button class="f-btn" @click="handleTransferDepartment(scope.row)" type="text"
+                                   size="small">变更部门
+                        </el-button>
+                        <el-button class="f-btn" @click="handleDeleteEmployee(scope.row)" type="text"
+                                   size="small">操作离职
+                        </el-button>
                     </template>
                 </el-table-column>
             </el-table>
         </el-container>
-        <el-drawer
-            title="操作离职"
-            :visible.sync="showDeleteEmployeeDrawer"
-            direction="rtl"
-            :before-close="onDeleteEmployee">
-            <DeleteEmployee
-                :employee="currentEmployee"
-                :on-delete-employee="onDeleteEmployee"/>
+        <el-container v-else>
+            <el-header>
+                <div
+                    style="display: flex; flex-direction: row; align-items: center; justify-content: center; height: 100%">
+                    请选择部门
+                </div>
+            </el-header>
+
+        </el-container>
+        <el-drawer title="操作离职" :visible.sync="showDeleteEmployeeDrawer" direction="rtl"
+                   :before-close="onDeleteEmployee">
+            <DeleteEmployee :employee="currentEmployee" :on-delete-employee="onDeleteEmployee"/>
         </el-drawer>
 
-        <el-dialog :visible.sync="showUpdateDepartmentDialog" :before-close="() => this.showUpdateDepartmentDialog= false">
-            <UpdateDepartment
-                :managers="checkedMembers"
-                :current-department="targetDepartment"
-                :parent-department="targetDepartment"
-                :on-update-department="onUpdateDepartment"
-                :on-choose-member="() => {this.checkedMembers = []; this.showChooseMemberDialog = true}"
-                :on-uncheck-member="onUncheckMember"
-            />
-            <el-dialog ref="dialog" :visible.sync="showChooseMemberDialog" append-to-body @hook:mounted="$refs.dialog.rendered = true">>
-                <ChooseMember :initial-checked-members="initialCheckedMembers" :max-choose-count="1" :on-cancel="()=> this.showChooseMemberDialog = false" :on-confirm="onCheckMember"/>
-            </el-dialog>
+        <el-dialog :visible.sync="showUpdateDepartmentDialog"
+                   destroy-on-close
+                   :before-close="() => this.showUpdateDepartmentDialog = false">
+            <UpdateDepartment :current-department="targetDepartment" :on-update-department="onUpdateDepartment"/>
         </el-dialog>
 
-        <el-dialog :visible.sync="showAddSubDepartmentDialog" :before-close="() => this.showAddSubDepartmentDialog = false">
-            <AddSubDepartment
-                :managers="checkedMembers"
-                :parent-department="currentOrg"
-                :on-add-department="onAddDepartment"
-                :on-choose-member="() => this.showChooseMemberDialog = true"
-                :on-uncheck-member="onUncheckMember"
-            />
-            <el-dialog ref="dialog" :visible.sync="showChooseMemberDialog" append-to-body @hook:mounted="$refs.dialog.rendered = true">>
-                <ChooseMember :initial-checked-members="initialCheckedMembers" :max-choose-count="1" :on-cancel="()=> this.showChooseMemberDialog = false" :on-confirm="onCheckMember"/>
-            </el-dialog>
+        <el-dialog :visible.sync="showAddSubDepartmentDialog"
+                   destroy-on-close
+                   :before-close="() => this.showAddSubDepartmentDialog = false">
+            <AddSubDepartment :parent-department="currentOrg" :on-add-department="onAddDepartment"/>
         </el-dialog>
-        <el-dialog
-            :visible.sync="showAddDepartmentMemberDialog"
-            :close-on-click-modal="false"
-            :before-close="() => {this.showAddDepartmentMemberDialog = false; this.checkedDepartments =[]}">
-            <AddDepartmentMember
-                :checked-departments="checkedDepartments"
-                :on-cancel="()=> this.showAddDepartmentMemberDialog = false"
-                :on-choose-department="() => this.showChooseDepartmentDialog = true"
-                :on-uncheck-department="onUncheckDepartment"
-            />
+
+        <el-dialog :visible.sync="showAddDepartmentMemberDialog" :close-on-click-modal="false"
+                   destroy-on-close
+                   :before-close="() => { this.showAddDepartmentMemberDialog = false; this.checkedDepartments = [] }">
+            <AddDepartmentMember :checked-departments="checkedDepartments"
+                                 :on-cancel="() => this.showAddDepartmentMemberDialog = false"
+                                 :on-choose-department="() => this.showChooseDepartmentDialog = true"
+                                 :on-uncheck-department="onUncheckDepartment"/>
             <el-dialog :visible.sync="showChooseDepartmentDialog" append-to-body>
-                <ChooseDepartment :target-department="currentOrg" :on-cancel="()=> this.showChooseDepartmentDialog = false" :on-confirm="onCheckDepartment"/>
+                <ChooseDepartment :target-department="currentOrg"
+                                  :on-cancel="() => this.showChooseDepartmentDialog = false" :on-confirm="onCheckDepartment"/>
+            </el-dialog>
+        </el-dialog>
+
+        <el-dialog title="变更部门" :visible.sync="showTransferDepartmentDialog" :close-on-click-modal="false"
+                   :before-close="() => { this.showTransferDepartmentDialog = false; this.transferDepartments = [] }">
+            <div v-if="employeeToTransfer">
+                <p>将 <b>{{ employeeToTransfer.name }}</b> 变更至:</p>
+                <el-input disabled style="margin: 20px 0;">
+                    <div v-if="transferDepartments && transferDepartments.length" slot="prepend">
+                        <el-tag v-for="(depart, index) in transferDepartments" :key="index" closable
+                                @close="onUncheckTransferDepartment(depart)" type="info">
+                            {{ depart && depart.name }}
+                        </el-tag>
+                    </div>
+                    <el-button slot="append" type="text" icon="el-icon-edit"
+                               @click="showTransferChooseDepartmentDialog = true"></el-button>
+                </el-input>
+                <div style="text-align: right; margin-top: 20px;">
+                    <el-button @click="showTransferDepartmentDialog = false">取消</el-button>
+                    <el-button type="primary" :disabled="transferDepartments.length === 0"
+                               @click="confirmTransferDepartment">确定
+                    </el-button>
+                </div>
+            </div>
+            <el-dialog :visible.sync="showTransferChooseDepartmentDialog" append-to-body>
+                <ChooseDepartment :on-cancel="() => this.showTransferChooseDepartmentDialog = false"
+                                  :on-confirm="onCheckTransferDepartment"/>
             </el-dialog>
         </el-dialog>
     </el-container>
 </template>
 
 <script>
-
-import {mapState} from "vuex";
+import {useOrgStore} from "@/store/stores/orgStore";
 import AddSubDepartment from "@/components/page/organization/dialog/AddSubDepartment";
 import AddDepartmentMember from "@/components/page/organization/dialog/AddDepartmentMember";
 import ChooseDepartment from "@/components/page/organization/dialog/ChooseDepartment";
-import ChooseMember from "@/components/page/organization/dialog/ChooseMember";
 import DeleteEmployee from "@/components/page/organization/drawer/DeleteEmployee";
 import UpdateDepartment from "@/components/page/organization/dialog/UpdateDepartment.vue";
-import fa from "element-ui/src/locale/lang/fa";
 import api from "@/api/api";
 
 export default {
     name: "Member",
-    components: {UpdateDepartment, DeleteEmployee, AddDepartmentMember, AddSubDepartment, ChooseDepartment, ChooseMember},
+    components: {UpdateDepartment, DeleteEmployee, AddDepartmentMember, AddSubDepartment, ChooseDepartment},
     data() {
         return {
             defaultProps: {
@@ -158,9 +164,7 @@ export default {
             multipleSelection: [],
 
             showDeleteEmployeeDrawer: false,
-
             showUpdateDepartmentDialog: false,
-
             showAddSubDepartmentDialog: false,
             showAddDepartmentMemberDialog: false,
 
@@ -172,18 +176,16 @@ export default {
             showChooseDepartmentDialog: false,
             checkedDepartments: [],
 
-            showChooseMemberDialog: false,
-            checkedMembers: [],
-
+            showTransferDepartmentDialog: false,
+            showTransferChooseDepartmentDialog: false,
+            employeeToTransfer: null,
+            transferDepartments: [],
         }
     },
     computed: {
-        initialCheckedMembers() {
-            return this.checkedMembers.map(m => m.employeeId);
-        },
-        ...mapState({
-            rootOrganizations: state => state.org.rootOrganizations,
-        })
+        rootOrganizations() {
+            return this.orgStore.rootOrganizations;
+        }
     },
     watch: {
         'currentOrg': {
@@ -202,9 +204,14 @@ export default {
         }
     },
 
+    setup() {
+        const orgStore = useOrgStore();
+        return {orgStore};
+    },
+
     activated() {
         if (this.rootOrganizations.length === 0) {
-            this.$store.dispatch('getRootOrganizationsWithChildren')
+            this.orgStore.getRootOrganizationsWithChildren()
                 .then(() => {
                     this.currentOrg = this.rootOrganizations[0];
                 })
@@ -218,21 +225,18 @@ export default {
         handleNodeClick(data) {
             console.log('node click', data)
             if (!data._orgWithChildren && data.id) {
-                this.$store.dispatch('queryOrganizationWithChildren', data)
+                this.orgStore.queryOrganizationWithChildren(data)
             }
             this.currentOrg = data;
         },
         async handleNodeExpand(data) {
             console.log('node expand', data);
-            // if (!data._orgWithChildren && data.id) {
-            //     await this.$store.dispatch('queryOrganizationWithChildren', data)
-            // }
         },
         async loadNode(node, resolve) {
             console.log('to load data', node)
             let data = node.data;
             if ((!data._orgWithChildren && data.id) || data._force) {
-                await this.$store.dispatch('queryOrganizationWithChildren', data)
+                await this.orgStore.queryOrganizationWithChildren(data)
                 console.log('load data', data);
                 this.currentOrg = data;
                 resolve(data.children);
@@ -244,22 +248,40 @@ export default {
             this.multipleSelection = val;
         },
         handleClickEmployee(data) {
-            // employee
             console.log('click employee', data)
+        },
+        handleDeleteEmployee(data) {
             this.showDeleteEmployeeDrawer = true;
             this.currentEmployee = data;
-            // this.$router.push('/organization/departmentanduser/department')
+        },
+        handleTransferDepartment(data) {
+            this.employeeToTransfer = data;
+            this.transferDepartments = [];
+            this.showTransferDepartmentDialog = true;
+        },
+        onCheckTransferDepartment(departments) {
+            this.transferDepartments = departments;
+            this.showTransferChooseDepartmentDialog = false;
+        },
+        onUncheckTransferDepartment(department) {
+            this.transferDepartments = this.transferDepartments.filter(d => d.id !== department.id);
+        },
+        confirmTransferDepartment() {
+            if (this.employeeToTransfer && this.transferDepartments.length > 0) {
+                let targetOrgIds = this.transferDepartments.map(d => d.id);
+                this.orgStore.transferEmployee(this.employeeToTransfer.employeeId, targetOrgIds)
+            }
+            this.showTransferDepartmentDialog = false
+            this.currentOrg = null;
         },
         importMember() {
             this.$router.push('/organization/departmentanduser/import-member')
         },
-
         handleDepartmentCommand(command) {
             console.log('handleDepartmentCommand', command)
             switch (command.c) {
                 case "add-sub":
                     this.showAddSubDepartmentDialog = true;
-                    this.targetParentDepartment = command.depart;
                     this.targetParentNode = command.node;
                     break;
                 case "edit":
@@ -268,7 +290,7 @@ export default {
                     this.targetNode = command.node;
                     break;
                 case "remove":
-                    this.$store.dispatch('removeOrganization', {organization: command.depart, dismissGroup: true})
+                    this.orgStore.removeOrganization({organization: command.depart, dismissGroup: true})
                         .then(() => {
                             let parentNode = command.node.parent;
                             this.updateTreeNode(parentNode);
@@ -285,24 +307,15 @@ export default {
             this.checkedDepartments = departments;
             this.showChooseDepartmentDialog = false;
         },
-        onCheckMember(members) {
-            this.showChooseMemberDialog = false;
-            this.checkedMembers = members;
-        },
-        onUncheckMember(member) {
-            this.checkedMembers = this.checkedMembers.filter(m => m.employeeId !== member.employeeId);
-        },
         onUncheckDepartment(department) {
             this.checkedDepartments = this.checkedDepartments.filter(d => d.id !== department.id);
         },
-
         onUpdateDepartment(success) {
             if (success) {
                 this.updateTreeNode(this.targetNode);
             }
             this.showUpdateDepartmentDialog = false;
         },
-
         onAddDepartment(success) {
             if (success) {
                 this.updateTreeNode(this.targetParentNode);
@@ -310,16 +323,12 @@ export default {
             }
             this.showAddSubDepartmentDialog = false;
         },
-
         onDeleteEmployee(success) {
             this.showDeleteEmployeeDrawer = false;
             if (success) {
-                this.$store.dispatch('queryOrganizationWithChildren', this.currentOrg);
+                this.orgStore.queryOrganizationWithChildren(this.currentOrg);
             }
         },
-
-        // el-tree 绑定的数据更新之后,并不会自动更新,故采用这种方案
-        // fyi: https://zhuanlan.zhihu.com/p/370597632
         updateTreeNode(node) {
             node.loaded = false;
             node.data._force = true;
@@ -330,7 +339,6 @@ export default {
 </script>
 
 <style scoped>
-
 .custom-tree-node {
     flex: 1 1 auto;
     display: flex;

+ 8 - 1
organization-web/src/components/page/organization/dialog/AddDepartmentMember.vue

@@ -57,6 +57,7 @@
 </template>
 
 <script>
+import { useOrgStore } from "@/store/stores/orgStore";
 
 export default {
     name: "AddDepartmentMember",
@@ -78,6 +79,12 @@ export default {
             required: true,
         }
     },
+
+    setup() {
+        const orgStore = useOrgStore();
+        return { orgStore };
+    },
+
     data() {
         return {
             employee: {},
@@ -113,7 +120,7 @@ export default {
         },
         onConfirm() {
             this.checkedDepartments.forEach(department => {
-                this.$store.dispatch('createEmployee', {
+                this.orgStore.createEmployee({
                     employee: this.employee,
                     targetOrg: department,
                 })

+ 35 - 16
organization-web/src/components/page/organization/dialog/AddSubDepartment.vue

@@ -15,7 +15,7 @@
             </el-form-item>
             <el-form-item label="部门负责人">
                 <el-input disabled>
-                    <div v-if="managers && managers.length > 0" slot="prepend">
+                    <div v-if="managers.length > 0" slot="prepend">
                         <el-tag
                             v-for="(member, index) in managers"
                             :key="index"
@@ -25,7 +25,7 @@
                             {{ member.name }}
                         </el-tag>
                     </div>
-                    <el-button slot="append" type="text" icon="el-icon-edit" @click="onChooseMember"></el-button>
+                    <el-button slot="append" type="text" icon="el-icon-edit" @click="showChooseMemberDialog = true"></el-button>
                 </el-input>
             </el-form-item>
             <el-form-item label="是否创建部门群">
@@ -36,53 +36,72 @@
             <el-button @click="onAddDepartment(false)">取消</el-button>
             <el-button type="primary" :disabled="!confirmButtonEnable" @click="onConfirm">确定</el-button>
         </div>
+
+        <!-- 集成 ChooseMember 对话框 -->
+        <el-dialog :visible.sync="showChooseMemberDialog" append-to-body title="选择成员">
+            <ChooseMember
+                :initial-checked-members="initialCheckedMembers"
+                :max-choose-count="1"
+                :on-cancel="() => this.showChooseMemberDialog = false"
+                :on-confirm="onCheckMember"/>
+        </el-dialog>
     </div>
 </template>
 
 <script>
+import { useOrgStore } from "@/store/stores/orgStore";
+import ChooseMember from "@/components/page/organization/dialog/ChooseMember";
 
 export default {
     name: "AddSubDepartment",
+    components: {ChooseMember},
     props: {
         parentDepartment: {
             type: Object,
             required: true,
         },
-        managers: {
-            type: Array,
-            required: true,
-        },
         onAddDepartment: {
             type: Function,
             required: true,
-        },
-        onChooseMember: {
-            type: Function,
-            required: true,
-        },
-        onUncheckMember: {
-            type: Function,
-            required: true,
         }
     },
+
+    setup() {
+        const orgStore = useOrgStore();
+        return { orgStore };
+    },
+
     data() {
         return {
             organization: {},
             createOrganizationGroup: false,
+            managers: [],
+            showChooseMemberDialog: false
         }
     },
     computed: {
         confirmButtonEnable() {
             return this.organization.name && this.managers.length === 1
+        },
+        initialCheckedMembers() {
+            return this.managers.map(m => m.employeeId);
         }
     },
     methods: {
         handleCloseTag(tag) {
-            this.onUncheckMember(tag);
+            this.managers = this.managers.filter(m => m.employeeId !== tag.employeeId);
+        },
+        onCheckMember(members) {
+            this.showChooseMemberDialog = false;
+            this.managers = members;
         },
         onConfirm() {
+            if (this.managers.length === 0) {
+                this.$message.error('请选择部门负责人');
+                return;
+            }
             this.organization.managerId = this.managers[0].employeeId;
-            this.$store.dispatch('createOrganization', {
+            this.orgStore.createOrganization({
                 organization: this.organization,
                 parentOrganization: this.parentDepartment,
                 createGroup: this.createOrganizationGroup,

+ 23 - 10
organization-web/src/components/page/organization/dialog/ChooseDepartment.vue

@@ -45,14 +45,14 @@
             </div>
         </div>
         <div class="action-container">
-            <el-button @click="onCancel">取消</el-button>
-            <el-button type="primary" @click="onConfirm(checkedDepartments)">确定</el-button>
+            <el-button @click="_onCancel">取消</el-button>
+            <el-button type="primary" @click="_onConfirm">确定</el-button>
         </div>
     </div>
 </template>
 
 <script>
-import {mapState} from "vuex";
+import {useOrgStore} from "@/store/stores/orgStore";
 import Eltree2 from '../../../../../vendor/tree/src/tree'
 
 export default {
@@ -72,6 +72,11 @@ export default {
         }
     },
 
+    setup() {
+        const orgStore = useOrgStore();
+        return {orgStore};
+    },
+
     data() {
         return {
             input: '',
@@ -80,18 +85,28 @@ export default {
     },
 
     computed: {
-        ...mapState({
-            rootOrganizations: state => state.org.rootOrganizations,
-        })
+        rootOrganizations() {
+            return this.orgStore.rootOrganizations;
+        }
     },
+
     mounted() {
         this.$refs.tree.setCheckedNodes([this.checkedDepartment])
     },
+
     methods: {
+        _onCancel() {
+            this.onCancel && this.onCancel();
+            this.checkedDepartments = [];
+        },
+        _onConfirm() {
+            this.onConfirm && this.onConfirm(this.checkedDepartments);
+            this.checkedDepartments = [];
+        },
         handleNodeClick(data) {
             console.log('node click', data)
             if (!data._orgWithChildren && data.id) {
-                this.$store.dispatch('queryOrganizationWithChildren', data)
+                this.orgStore.queryOrganizationWithChildren(data)
             }
             this.currentOrg = data;
         },
@@ -99,7 +114,7 @@ export default {
             console.log('to load data', node)
             let data = node.data;
             if (!data._orgWithChildren && data.id) {
-                await this.$store.dispatch('queryOrganizationWithChildren', data)
+                await this.orgStore.queryOrganizationWithChildren(data)
                 this.currentOrg = data;
                 resolve(data.children);
             } else {
@@ -161,6 +176,4 @@ export default {
     justify-content: flex-end;
     margin-right: 20px;
 }
-
-
 </style>

+ 12 - 9
organization-web/src/components/page/organization/dialog/ChooseMember.vue

@@ -1,6 +1,5 @@
 <template>
     <div>
-        <p class="title">请选择成员</p>
         <div class="container">
             <div class="org-container">
                 <el-input class="input" v-model="input" placeholder="请输入成员名称"></el-input>
@@ -54,7 +53,7 @@
 </template>
 
 <script>
-import {mapState} from "vuex";
+import {useOrgStore} from "@/store/stores/orgStore";
 import Eltree2 from '../../../../../vendor/tree/src/tree'
 
 export default {
@@ -81,6 +80,11 @@ export default {
         }
     },
 
+    setup() {
+        const orgStore = useOrgStore();
+        return {orgStore};
+    },
+
     data() {
         return {
             treeDefaultProps: {
@@ -94,10 +98,11 @@ export default {
     },
 
     computed: {
-        ...mapState({
-            rootOrganizations: state => state.org.rootOrganizations,
-        })
+        rootOrganizations() {
+            return this.orgStore.rootOrganizations;
+        }
     },
+
     mounted() {
         this.$refs.tree.setCheckedNodes(this.initialCheckedMembers);
     },
@@ -106,7 +111,7 @@ export default {
         handleNodeClick(data) {
             console.log('node click', data)
             if (!data._orgWithChildren && data.id) {
-                this.$store.dispatch('queryOrganizationWithChildren', data)
+                this.orgStore.queryOrganizationWithChildren(data)
             }
             this.currentOrg = data;
         },
@@ -117,7 +122,7 @@ export default {
                 data.employees = [];
             }
             if (!data._orgWithChildren && data.id) {
-                await this.$store.dispatch('queryOrganizationWithChildren', data)
+                await this.orgStore.queryOrganizationWithChildren(data)
                 this.currentOrg = data;
                 let employees = data.employees.map(e => {
                     return {
@@ -196,6 +201,4 @@ export default {
     color: red;
     margin-right: 20px;
 }
-
-
 </style>

+ 51 - 63
organization-web/src/components/page/organization/dialog/UpdateDepartment.vue

@@ -1,31 +1,19 @@
 <template>
     <div>
         <p class="title">更新部门</p>
-        <el-form
-            label-position="right"
-            :model="updatedOrganization"
-            size="medium"
-            class="demo-form-inline">
+        <el-form label-position="right" :model="updatedOrganization" size="medium" class="demo-form-inline">
             <el-form-item label="部门名称">
                 <el-input v-model.trim="updatedOrganization.name" placeholder="部门名称"></el-input>
             </el-form-item>
-            <el-form-item label="上级部门">
-                <el-input disabled :value="parentDepartment.name">
-                </el-input>
-            </el-form-item>
             <el-form-item label="部门负责人">
                 <el-input disabled>
-                    <div v-if="managers && managers.length > 0" slot="prepend">
-                        <el-tag
-                            v-for="(member, index) in managers"
-                            :key="index"
-                            closable
-                            @close="handleCloseTag(member)"
-                            type="info">
-                            {{ member.name }}
+                    <div slot="prepend">
+                        <el-tag v-if="computedManager" closable @close="handleCloseTag(manager)" type="info">
+                            {{ computedManager.name }}
                         </el-tag>
                     </div>
-                    <el-button slot="append" type="text" icon="el-icon-edit" @click="onChooseMember"></el-button>
+                    <el-button slot="append" type="text" icon="el-icon-edit"
+                               @click="showChooseMemberDialog = true"></el-button>
                 </el-input>
             </el-form-item>
             <el-form-item v-if="!currentDepartment.groupId" label="是否创建部门群">
@@ -36,100 +24,101 @@
             <el-button @click="onUpdateDepartment(false)">取消</el-button>
             <el-button type="primary" :disabled="!confirmButtonEnable" @click="onConfirm">确定</el-button>
         </div>
+
+        <!-- 集成 ChooseMember 对话框 -->
+        <el-dialog :visible.sync="showChooseMemberDialog" append-to-body title="选择成员" destroy-on-close>
+            <ChooseMember :initial-checked-members="initialCheckedMembers" :max-choose-count="1"
+                          :on-cancel="() => this.showChooseMemberDialog = false" :on-confirm="onCheckMember"/>
+        </el-dialog>
     </div>
 </template>
 
 <script>
-
+import {useOrgStore} from "@/store/stores/orgStore";
 import api from "@/api/api";
-import fa from "element-ui/src/locale/lang/fa";
+import ChooseMember from "@/components/page/organization/dialog/ChooseMember";
 
 export default {
     name: "UpdateDepartment",
+    components: {ChooseMember},
     props: {
         currentDepartment: {
             type: Object,
             required: true,
         },
-        parentDepartment: {
-            type: Object,
-            required: true,
-        },
-        managers: {
-            type: Array,
-            required: true,
-        },
         onUpdateDepartment: {
             type: Function,
             required: true,
-        },
-        onChooseMember: {
-            type: Function,
-            required: true,
-        },
-        onUncheckMember: {
-            type: Function,
-            required: true,
         }
     },
+
+    setup() {
+        const orgStore = useOrgStore();
+        return {orgStore};
+    },
+
     data() {
         return {
             updatedOrganization: {
                 name: this.currentDepartment.name,
             },
             createOrganizationGroup: !this.currentDepartment.groupId,
+            manager: null,
+            showChooseMemberDialog: false,
         }
     },
     computed: {
-        fa() {
-            return fa
-        },
         confirmButtonEnable() {
-            return this.updatedOrganization.name && this.managers.length === 1
+            return this.updatedOrganization.name && this.manager;
         },
-    },
-    async mounted() {
+        initialCheckedMembers() {
+            return this.manager ? [this.manager.employeeId] : [];
+        },
+        computedManager() {
+            return this.manager ? this.manager : this.currentDepartment.employees.filter(m => m.employeeId === this.currentDepartment.managerId)[0];
+        }
     },
 
     methods: {
-        handleCloseTag(tag) {
-            this.onUncheckMember(tag);
+        handleCloseTag() {
+            this.manager = null;
+        },
+        onCheckMember(members) {
+            this.showChooseMemberDialog = false;
+            console.log('onCheckMembers ', members)
+            if (members && members.length > 0) {
+                this.manager = members[0];
+            }
         },
         onConfirm() {
-            this.updatedOrganization.managerId = this.managers[0].employeeId;
+            if (!this.manager) {
+                this.$message.error('请选择部门负责人');
+                return;
+            }
+
+            this.updatedOrganization.managerId = this.manager.employeeId;
             this.currentDepartment.name = this.updatedOrganization.name;
-            this.currentDepartment.managerId = this.managers[0].employeeId;
-            this.$store.dispatch('updateOrganization', {
-                organization: this.currentDepartment
-            })
+            this.currentDepartment.managerId = this.manager.employeeId;
+
+            this.orgStore.updateOrganization(this.currentDepartment)
                 .then(res => {
-                    console.log('create organization success', res)
+                    console.log('更新部门成功', res)
                     this.onUpdateDepartment(true);
                 })
                 .catch(err => {
-                    console.log('create organization error', err)
+                    console.log('更新部门失败', err)
                     this.onUpdateDepartment(false);
                 })
+
             if (this.createOrganizationGroup) {
                 api.createOrganizationGroup(this.currentDepartment.id, '')
             }
-        },
-    },
-
-    watch: {
-        organization: {
-            async handler() {
-                let employee = await api.queryEmployee(this.currentDepartment.managerId)
-                this.managers.push(employee);
-            },
-            immediate: true,
         }
     }
 }
 </script>
 
 <style scoped>
-
 .title {
     position: absolute;
     top: 20px;
@@ -147,5 +136,4 @@ export default {
     justify-content: flex-end;
     margin-right: 10px;
 }
-
 </style>

+ 9 - 1
organization-web/src/components/page/organization/drawer/DeleteEmployee.vue

@@ -25,6 +25,8 @@
 </template>
 
 <script>
+import { useOrgStore } from "@/store/stores/orgStore";
+
 export default {
     name: "DeleteEmployee",
     props: {
@@ -37,6 +39,12 @@ export default {
             required: true,
         }
     },
+
+    setup() {
+        const orgStore = useOrgStore();
+        return { orgStore };
+    },
+
     data() {
         return {
             destroyIMUser: false,
@@ -44,7 +52,7 @@ export default {
     },
     methods: {
         onConfirm() {
-            this.$store.dispatch('deleteEmployee', {employeeId: this.employee.employeeId, destroyIMUser: this.destroyIMUser})
+            this.orgStore.deleteEmployee({employeeId: this.employee.employeeId, destroyIMUser: this.destroyIMUser})
                 .then(() => {
                     this.onDeleteEmployee(true);
                 })

+ 2 - 2
organization-web/src/main.js

@@ -4,7 +4,7 @@ import Vue from 'vue';
 import App from './App';
 import router from './router';
 import ElementUI from 'element-ui';
-import store from './store';
+import pinia from './store/pinia';
 import 'element-ui/lib/theme-chalk/index.css'; // 默认主题
 import VueI18n from 'vue-i18n'
 
@@ -29,7 +29,7 @@ const i18n = new VueI18n({
 export default new Vue({
     el: '#app',
     router,
-    store,
+    pinia,
     i18n,
     components: {App},
     render: h => h(App)

+ 3 - 0
organization-web/src/model/organization.js

@@ -18,6 +18,7 @@ export default class Organization {
     // for ui
     label;
     type;
+    managerName;
     // subDepartments
     children;
     hasChildren = true;
@@ -26,6 +27,8 @@ export default class Organization {
     buildRenderData(organizationWithChildren) {
         this.label = this.name;
         this.type = 1;
+        let manager = organizationWithChildren ? organizationWithChildren.employees.find(e => e.employeeId === this.managerId) : null
+        this.managerName = manager ? manager.name : null;
         this.children = [];
         this.employees = [];
         if (!organizationWithChildren) {

+ 1 - 1
organization-web/src/router/index.js

@@ -30,7 +30,7 @@ export default new Router({
                 },
                 {
                     path: '/organization/departmentanduser',
-                    component: resolve => require(['../components/page/organization/DepartmentAndMember.vue'], resolve),
+                    component: resolve => require(['../components/page/organization/Member.vue'], resolve),
                     meta: {title: '成员与部门'},
                     // children: [
                     //     {

+ 0 - 93
organization-web/src/store/components/org_store.js

@@ -1,93 +0,0 @@
-import Api from '@/api/api'
-import OrganizationWithChildren from "@/model/organizationWithChildren";
-import Organization from "@/model/organization";
-
-export default {
-    state: {
-        rootOrganizations: [],
-        _findOrganization(org, orgId) {
-            if (org.id === orgId) {
-                return org;
-            } else if (org.children && org.children.length) {
-                for (let i = 0; i < org.children.length; i++) {
-                    let child = org.children[i];
-                    let result = this._findOrganization(child, orgId);
-                    if (result) {
-                        return result;
-                    }
-                }
-            } else {
-                return null;
-            }
-        }
-    },
-
-    mutations: {},
-    actions: {
-        async getRootOrganizationsWithChildren({state}) {
-            let rootOrgs = await Api.getRootOrganization()
-            state.rootOrganizations = [];
-            for (let i = 0; i < rootOrgs.length; i++) {
-                let rootOrg = rootOrgs[i];
-                let org = Object.assign(new Organization(), rootOrg);
-                state.rootOrganizations.push(org);
-                let tmp = await Api.queryOrganizationWithChildren(rootOrg.id);
-                let orgWC = Object.assign(new OrganizationWithChildren(), tmp);
-                org._orgWithChildren = orgWC;
-                org.buildRenderData(orgWC);
-                console.log('query root organizationWithChildren', state.rootOrganizations);
-            }
-        },
-
-        async queryOrganizationWithChildren({state}, org) {
-            let result = await Api.queryOrganizationWithChildren(org.id);
-            let orgWC = Object.assign(new OrganizationWithChildren(), result);
-            org._orgWithChildren = orgWC;
-            org.buildRenderData(orgWC);
-            console.log('queryOrganizationWithChildren', org.id, state)
-        },
-
-        async createEmployee({dispatch}, {employee, targetOrg}) {
-            employee.organizationId = targetOrg.id;
-            await Api.createEmployee(employee);
-            dispatch('queryOrganizationWithChildren', targetOrg)
-        },
-
-        async createOrganization({dispatch}, {parentOrganization, organization, createGroup}) {
-            organization.parentId = parentOrganization.id;
-            let result = await Api.createOrganization(organization);
-            // dispatch('queryOrganizationWithChildren', parentOrganization);
-            if (createGroup) {
-                await Api.createOrganizationGroup(result.organizationId);
-            }
-        },
-
-        async updateOrganization({dispatch}, organization) {
-            await Api.updateOrganization(organization);
-        },
-
-        async removeOrganization({state}, {organization, dismissGroup}) {
-            await Api.deleteOrganization(organization);
-            // let parent = state._findOrganization(state.rootOrganizations[0], organization.parentId)
-            // dispatch('queryOrganizationWithChildren', parent);
-            if (dismissGroup && organization.groupId) {
-                try {
-                    await Api.dismissOrganizationGroup(organization);
-                } catch (e) {
-                    console.error('dismissGroup error', e);
-                }
-            }
-        },
-
-        async queryEmployee({state}, {employeeId}) {
-            await Api.queryEmployee(employeeId);
-            // TODO 更新当前部门?
-        },
-
-        async deleteEmployee({state}, {employeeId, destroyIMUser}) {
-            await Api.deleteEmployee(employeeId, destroyIMUser);
-            // TODO 更新当前部门?
-        }
-
-    }
-}

+ 0 - 29
organization-web/src/store/components/user_store.js

@@ -1,29 +0,0 @@
-import Api from '@/api/api'
-
-export default {
-    state: {
-        account: ''
-    },
-    mutations: {
-        setAccount(state, account) {
-            state.account = account;
-        }
-    },
-    actions: {
-        login({commit}, payload) {
-            console.log('login', commit)
-            return Api.login(payload);
-        },
-
-        getAccount({state}) {
-            Api.getAccount().then((account) => {
-                state.account = account;
-            })
-        },
-
-        updatePwd({commit}, payload) {
-            console.log('updatePwd', commit)
-            return Api.udpatePwd(payload)
-        },
-    }
-}

+ 8 - 0
organization-web/src/store/pinia.js

@@ -0,0 +1,8 @@
+import Vue from 'vue';
+import { createPinia, PiniaVuePlugin } from 'pinia';
+
+Vue.use(PiniaVuePlugin);
+
+const pinia = createPinia();
+
+export default pinia;

+ 96 - 0
organization-web/src/store/stores/orgStore.js

@@ -0,0 +1,96 @@
+import { defineStore } from 'pinia';
+import Api from '@/api/api';
+import OrganizationWithChildren from "@/model/organizationWithChildren";
+import Organization from "@/model/organization";
+
+export const useOrgStore = defineStore('org', {
+  state: () => ({
+    rootOrganizations: []
+  }),
+
+  actions: {
+    _findOrganization(org, orgId) {
+      if (org.id === orgId) {
+        return org;
+      } else if (org.children && org.children.length) {
+        for (let i = 0; i < org.children.length; i++) {
+          let child = org.children[i];
+          let result = this._findOrganization(child, orgId);
+          if (result) {
+            return result;
+          }
+        }
+      } else {
+        return null;
+      }
+    },
+
+    async getRootOrganizationsWithChildren() {
+      let rootOrgs = await Api.getRootOrganization();
+      this.rootOrganizations = [];
+      for (let i = 0; i < rootOrgs.length; i++) {
+        let rootOrg = rootOrgs[i];
+        let org = Object.assign(new Organization(), rootOrg);
+        this.rootOrganizations.push(org);
+        let tmp = await Api.queryOrganizationWithChildren(rootOrg.id);
+        let orgWC = Object.assign(new OrganizationWithChildren(), tmp);
+        org._orgWithChildren = orgWC;
+        org.buildRenderData(orgWC);
+        console.log('query root organizationWithChildren', this.rootOrganizations);
+      }
+    },
+
+    async queryOrganizationWithChildren(org) {
+      let result = await Api.queryOrganizationWithChildren(org.id);
+      let orgWC = Object.assign(new OrganizationWithChildren(), result);
+      org._orgWithChildren = orgWC;
+      org.buildRenderData(orgWC);
+      console.log('queryOrganizationWithChildren', org.id);
+    },
+
+    async createEmployee({employee, targetOrg}) {
+      employee.organizationId = targetOrg.id;
+      await Api.createEmployee(employee);
+      this.queryOrganizationWithChildren(targetOrg);
+    },
+
+      async transferEmployee(employeeId, targetOrgIds) {
+          await Api.moveEmployee({
+              employeeId: employeeId,
+              organizations: targetOrgIds,
+          });
+          this.getRootOrganizationsWithChildren()
+      },
+
+    async createOrganization({parentOrganization, organization, createGroup}) {
+      organization.parentId = parentOrganization.id;
+      let result = await Api.createOrganization(organization);
+      if (createGroup) {
+        await Api.createOrganizationGroup(result.organizationId);
+      }
+    },
+
+    async updateOrganization(organization) {
+      await Api.updateOrganization(organization);
+    },
+
+    async removeOrganization({organization, dismissGroup}) {
+      await Api.deleteOrganization(organization);
+      if (dismissGroup && organization.groupId) {
+        try {
+          await Api.dismissOrganizationGroup(organization);
+        } catch (e) {
+          console.error('dismissGroup error', e);
+        }
+      }
+    },
+
+    async queryEmployee({employeeId}) {
+      await Api.queryEmployee(employeeId);
+    },
+
+    async deleteEmployee({employeeId, destroyIMUser}) {
+      await Api.deleteEmployee(employeeId, destroyIMUser);
+    }
+  }
+});

+ 29 - 0
organization-web/src/store/stores/userStore.js

@@ -0,0 +1,29 @@
+import { defineStore } from 'pinia';
+import Api from '@/api/api';
+
+export const useUserStore = defineStore('user', {
+  state: () => ({
+    account: ''
+  }),
+
+  actions: {
+    setAccount(account) {
+      this.account = account;
+    },
+
+    login(payload) {
+      console.log('login');
+      return Api.login(payload);
+    },
+
+    async getAccount() {
+      const account = await Api.getAccount();
+      this.account = account;
+    },
+
+    updatePwd(payload) {
+      console.log('updatePwd');
+      return Api.udpatePwd(payload);
+    }
+  }
+});

Some files were not shown because too many files changed in this diff