index.vue 37 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003
  1. <template>
  2. <div class="h-full flex flex-col teamstyle overflow-hidden temaClass">
  3. <!-- 头部 -->
  4. <div class="bg-white flex justify-between team-header">
  5. <div class="flex items-center">
  6. <el-link type="primary" class="text-nowrap mr-20" :icon="CirclePlusFilled"
  7. @click="dialogFromCli('addDeptDialogVisible')">创建部门</el-link>
  8. <el-link class="text-nowrap textFont textFont mr-10" type="primary" :icon="Edit"
  9. @click="updateDepartment('addDeptDialogVisible')">
  10. <template v-if="!deptListItem.labe">全部人员</template>
  11. <template v-if="deptListItem.labe">
  12. <TextTranslation translationTypes="departmentName" :translationValue="deptListItem.labe"></TextTranslation>
  13. </template>
  14. </el-link>
  15. <span class="textSpan textFont">共 0 人</span>
  16. </div>
  17. <div class="teamForm flex items-center">
  18. <el-input v-model="teamForm.keyword" style="max-width: 650px" size="default" placeholder="请输入姓名搜索" class="mr-6"
  19. clearable @clear="getTableData()" @keyup.enter.native="getTableData()">
  20. <template #prepend>
  21. <el-select v-model="teamForm.matchingType" style="width: 80px">
  22. <el-option label="姓名" :value="0" />
  23. <el-option label="电话" :value="1" />
  24. <el-option label="工号" :value="2" />
  25. </el-select>
  26. </template>
  27. <template #append>
  28. <el-button :icon="Search" @click="getTableData()" />
  29. </template>
  30. </el-input>
  31. <div class="formItem mr-6 flex items-center">
  32. <div class="text-nowrap">状态:</div>
  33. <el-select v-model="teamForm.status" placeholder="请选择" size="default" style="width: 100px"
  34. @change="getTableData()">
  35. <el-option v-for="item in stateOptions" :key="item.value" :label="item.label" :value="item.value" />
  36. </el-select>
  37. </div>
  38. <div class="formItem mr-6 flex items-center">
  39. <div class="text-nowrap">角色:</div>
  40. <el-select v-model="teamForm.roleId" placeholder="请选择" size="default" style="width: 150px" clearable
  41. @change="getTableData()">
  42. <el-option v-for="item in roleList" :key="item.id" :label="item.rolename" :value="item.id" />
  43. </el-select>
  44. </div>
  45. <!-- <el-button type="primary" v-if="userInfo.userNameNeedTranslate == 1"
  46. @click="transitionOperation('exportUser', '')" v-permission="['teamExport']">导出人员</el-button>
  47. <el-button type="primary" v-if="userInfo.userNameNeedTranslate == 1"
  48. @click="dialogFrom.newSyncWithCorpWxDayloadVisable = true">同步企微通讯录</el-button>
  49. <el-button type="primary" v-if="userInfo.userNameNeedTranslate == 1" @click="officialAccountSetting()">{{
  50. officialAccountInformation.id ? '修改公众号配置' : '配置公众号' }}</el-button>
  51. <el-dropdown v-if="userInfo.userNameNeedTranslate != 1">
  52. <el-button type="primary">
  53. 更多操作<el-icon class="el-icon--right"><arrow-down /></el-icon>
  54. </el-button>
  55. <template #dropdown>
  56. <el-dropdown-menu>
  57. <el-dropdown-item @click="addPersone(false)" v-permission="['teamAdd']">添加人员</el-dropdown-item>
  58. <el-dropdown-item @click="transitionOperation('exportUser', '')"
  59. v-permission="['teamExport']">导出人员</el-dropdown-item>
  60. <el-dropdown-item @click="transitionOperation('importUser', '')"
  61. v-permission="['teamImport']">批量导入</el-dropdown-item>
  62. <el-dropdown-item @click="officialAccountSetting()">
  63. {{ officialAccountInformation.id ? '修改公众号配置' : '配置公众号' }}
  64. </el-dropdown-item>
  65. <el-dropdown-item @click="oneClickGenerationOfQrCode()" v-if="officialAccountInformation.id">
  66. 一键生成员工二维码
  67. </el-dropdown-item>
  68. <el-dropdown-item @click="exportQrCode()" v-if="officialAccountInformation.id">
  69. 导出员工二维码
  70. </el-dropdown-item>
  71. </el-dropdown-menu>
  72. </template>
  73. </el-dropdown> -->
  74. <el-dropdown>
  75. <el-button type="primary">
  76. 更多操作<el-icon class="el-icon--right"><arrow-down /></el-icon>
  77. </el-button>
  78. <template #dropdown>
  79. <el-dropdown-menu>
  80. <template v-if="userInfo.userNameNeedTranslate != 1">
  81. <el-dropdown-item @click="addPersone(false)" v-permission="['teamAdd']">添加人员</el-dropdown-item>
  82. <el-dropdown-item @click="transitionOperation('exportUser', '')" v-permission="['teamExport']">导出人员</el-dropdown-item>
  83. <el-dropdown-item @click="transitionOperation('importUser', '')" v-permission="['teamImport']">批量导入</el-dropdown-item>
  84. </template>
  85. <template v-if="userInfo.userNameNeedTranslate == 1">
  86. <el-dropdown-item @click="transitionOperation('exportUser', '')" v-permission="['teamExport']">导出人员</el-dropdown-item>
  87. <el-dropdown-item @click="dialogFrom.newSyncWithCorpWxDayloadVisable = true" v-permission="['teamAdd']">同步企微通讯录</el-dropdown-item>
  88. </template>
  89. <el-dropdown-item @click="officialAccountSetting()">
  90. {{ officialAccountInformation.id ? '修改公众号配置' : '配置公众号' }}
  91. </el-dropdown-item>
  92. <el-dropdown-item @click="oneClickGenerationOfQrCode()" v-if="officialAccountInformation.id">
  93. 一键生成员工二维码
  94. </el-dropdown-item>
  95. <el-dropdown-item @click="exportQrCode()" v-if="officialAccountInformation.id">
  96. 导出员工二维码
  97. </el-dropdown-item>
  98. </el-dropdown-menu>
  99. </template>
  100. </el-dropdown>
  101. </div>
  102. </div>
  103. <!-- 内容 -->
  104. <div class="flex-1 flex">
  105. <div class="p-4 w-80 pr-0">
  106. <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col">
  107. <div class="flex-1 overflow-y-auto const-left">
  108. <el-tree style="max-width: 600px" :data="deptList" :props="treeProps" @node-click="treeNode">
  109. <template #default="{ node, data }">
  110. <div class="flex justify-between treeContent">
  111. <div class="custom-tree-node" @mouseleave="mouseleave(data, $event)"
  112. @mouseover="mouseover(data, $event)">
  113. <div class="treeLabel">
  114. <TextTranslation translationTypes="departmentName" :translationValue="node.label">
  115. </TextTranslation>
  116. </div>
  117. <div class="treeIcon nodeEle" id="treeIcon" v-if="data.id > 0">
  118. <el-link type="primary" :icon="CirclePlus" :underline="false"
  119. @click.stop="dialogFromCli('addDeptDialogVisible', data, true)"></el-link>
  120. <el-link type="primary" :icon="Delete" :underline="false" style="margin-left: 6px;"
  121. @click.stop="deteleDept(data)"></el-link>
  122. </div>
  123. </div>
  124. </div>
  125. </template>
  126. </el-tree>
  127. </div>
  128. </div>
  129. </div>
  130. <div class="flex-1 p-4 overflow-auto">
  131. <div class="bg-white w-full h-full shadow-md rounded-md flex flex-col overflow-hidden pt-2 pl-2 pr-2">
  132. <div class="flex-1">
  133. <el-table ref="multipleTableRef" :data="tableData" v-loading="loadingFrom.tableLoading"
  134. @selection-change="changeBatch" style="width: 100%;height: calc(100vh - 204px);">
  135. <el-table-column type="selection" width="55" />
  136. <el-table-column label="姓名" property="name" width="150">
  137. <template #default="scope">
  138. <div class="flex items-center">
  139. <TextTranslation translationTypes="userName" :translationValue="scope.row.name"></TextTranslation>
  140. <template v-if="officialAccountInformation.id && scope.row.wxImgUrlWithTicket">
  141. <el-tooltip class="box-item" effect="dark" content="点击查看销售二维码" placement="top">
  142. <div class="ml-2 cursor-pointer" @click="viewQrCode(scope.row)">
  143. <el-icon color="#075985">
  144. <PictureFilled />
  145. </el-icon>
  146. </div>
  147. </el-tooltip>
  148. </template>
  149. </div>
  150. </template>
  151. </el-table-column>
  152. <el-table-column label="手机" property="phone"></el-table-column>
  153. <el-table-column label="工号" property="jobNumber"></el-table-column>
  154. <el-table-column label="部门" property="departmentName">
  155. <template #default="scope">
  156. <TextTranslation translationTypes="departmentName" :translationValue="scope.row.departmentName">
  157. </TextTranslation>
  158. </template>
  159. </el-table-column>
  160. <el-table-column label="角色" property="roleName"></el-table-column>
  161. <el-table-column label="创建时间" property="createTime"></el-table-column>
  162. <el-table-column label="操作" width="200" fixed="right">
  163. <template #default="scope">
  164. <el-button :size="'small'" @click="resetPwd(scope.row)">重置</el-button>
  165. <el-button type="primary" :size="'small'" @click="addPersone(scope.row)">编辑</el-button>
  166. <el-button :size="'small'" @click="transitionOperation('disable', scope.row)"
  167. v-if="scope.row.isActive == 1">停用</el-button>
  168. <el-button type="success" :size="'small'" @click="enableUser(scope.row)"
  169. v-if="scope.row.isActive == 0">启用</el-button>
  170. </template>
  171. </el-table-column>
  172. </el-table>
  173. </div>
  174. <div class="flex justify-between pb-2 pt-2 pl-3 pr-3">
  175. <div class="flex">
  176. <el-button size="default" @click="changeBatch(false)"
  177. :disabled="batchTableData.length == 0">取消</el-button>
  178. <el-dropdown class="ml-3">
  179. <el-button type="primary">
  180. 更多操作<el-icon class="el-icon--right"><arrow-down /></el-icon>
  181. </el-button>
  182. <template #dropdown>
  183. <el-dropdown-menu>
  184. <el-dropdown-item @click="batchItem('批量修改部门', 'dept', deptList)"
  185. :disabled="batchTableData.length == 0">批量修改部门</el-dropdown-item>
  186. <el-dropdown-item @click="batchItem('批量修改角色', 'role', roleList)"
  187. :disabled="batchTableData.length == 0">批量修改角色</el-dropdown-item>
  188. <el-dropdown-item @click="batchEnableItem"
  189. :disabled="batchTableData.length == 0">批量启用员工</el-dropdown-item>
  190. </el-dropdown-menu>
  191. </template>
  192. </el-dropdown>
  193. </div>
  194. <div class="pr-4">
  195. <el-pagination layout="total, prev, pager, next, sizes" :total="totalTable" :page-size="teamForm.pageSize"
  196. @size-change="handleSizeChange" @current-change="handleCurrentChange" />
  197. </div>
  198. </div>
  199. </div>
  200. </div>
  201. </div>
  202. <!-- 新增部门 -->
  203. <el-dialog v-model="dialogFrom.addDeptDialogVisible" width="600" :show-close="false" :before-close="handleClose">
  204. <template #header="{ close, titleId, titleClass }">
  205. <div class="flex justify-between items-center border-b pb-3 dialog-header">
  206. <h4 :id="titleId">{{ deptListItem.label || '创建部门' }}</h4>
  207. <div class="flex">
  208. <el-button @click="dialogFrom.addDeptDialogVisible = false">取消</el-button>
  209. <el-button type="primary" @click="createDepartment(deptRuleFormRef)"
  210. v-bind:loading="loadingFrom.deptDialogVisibleLoading">
  211. 确定
  212. </el-button>
  213. </div>
  214. </div>
  215. </template>
  216. <div class="pt-5">
  217. <el-form ref="deptRuleFormRef" style="max-width: 500px" :model="deptForm" :rules="deptRules" label-width="140px"
  218. size="large" status-icon>
  219. <el-form-item label="部门名称" prop="name">
  220. <el-input v-model="deptForm.name" placeholder="请输入部门名称" clearable
  221. :disabled="userInfo.userNameNeedTranslate == 1" />
  222. </el-form-item>
  223. <el-form-item label="主要负责人">
  224. <!-- <el-select v-model="deptForm.managerId" placeholder="请选择" style="width: 100%" clearable>
  225. <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id" />
  226. </el-select> -->
  227. <personnel-search v-model="deptForm.managerId" :size="''" placeholder="请选择"></personnel-search>
  228. </el-form-item>
  229. <el-form-item label="其他负责人">
  230. <!-- <el-select v-model="deptForm.otherManagerIds" placeholder="请选择" style="width: 100%" multiple clearable>
  231. <el-option v-for="item in userList" :key="item.id" :label="item.name" :value="item.id" />
  232. </el-select> -->
  233. <personnel-search v-model="deptForm.otherManagerIds" :size="''" multiple
  234. placeholder="请选择"></personnel-search>
  235. </el-form-item>
  236. </el-form>
  237. </div>
  238. </el-dialog>
  239. <!-- 停用 -->
  240. <el-dialog v-model="dialogFrom.resignationVisible" width="600" :show-close="false" :before-close="handleClose">
  241. <template #header="{ close, titleId, titleClass }">
  242. <div class="flex justify-between items-center border-b pb-3 dialog-header">
  243. <h4 :id="titleId">离职停用员工</h4>
  244. <div class="flex">
  245. <el-button @click="dialogFrom.resignationVisible = false">取消</el-button>
  246. <el-button type="primary" @click="resignation" v-bind:loading="loadingFrom.resignationLoading">
  247. 确定
  248. </el-button>
  249. </div>
  250. </div>
  251. </template>
  252. <div class="pt-4 pb-2">
  253. <div class="flex items-center justify-center">
  254. <div class="pr-2">员工离职日期:</div>
  255. <el-date-picker v-model="resignationDate" type="date" placeholder="请选择日期" value-format="YYYY-MM-DD"
  256. :clearable="false" />
  257. </div>
  258. </div>
  259. </el-dialog>
  260. <!-- 导出人员列表 -->
  261. <el-dialog v-model="dialogFrom.exportUserVisible" width="600" :show-close="false" :before-close="handleClose">
  262. <template #header="{ close, titleId, titleClass }">
  263. <div class="flex justify-between items-center border-b pb-3 dialog-header">
  264. <h4 :id="titleId">导出人员列表</h4>
  265. <div class="flex">
  266. <el-button @click="dialogFrom.exportUserVisible = false">取消</el-button>
  267. <el-button type="primary" @click="exportUser" v-bind:loading="loadingFrom.exportUserLoading">
  268. 导出
  269. </el-button>
  270. </div>
  271. </div>
  272. </template>
  273. <div class="pt-4 pb-2">
  274. <div class="flex items-center justify-center">
  275. <div class="pr-2">导出:</div>
  276. <el-radio-group v-model="exportRadio" class="ml-4">
  277. <el-radio value="1" size="large">全部人员</el-radio>
  278. <el-radio value="0" size="large">仅在职人员</el-radio>
  279. </el-radio-group>
  280. </div>
  281. </div>
  282. </el-dialog>
  283. <!-- 人员导入 -->
  284. <el-dialog v-model="dialogFrom.importVisible" width="680" :show-close="false" top="10vh">
  285. <template #header="{ close, titleId, titleClass }">
  286. <div class="flex justify-between items-center border-b pb-3 dialog-header">
  287. <h4 :id="titleId">人员批量导入</h4>
  288. <div class="flex">
  289. <el-button class="mr-3" @click="dialogFrom.importVisible = false">取消</el-button>
  290. <el-upload ref="importUserRef" class="upload-demo" :limit="1" :show-file-list="false" accept=".xlsx"
  291. :http-request="importUser">
  292. <el-button type="primary" :loading="loadingFrom.importLoading">导入</el-button>
  293. </el-upload>
  294. </div>
  295. </div>
  296. </template>
  297. <div class="p-8">
  298. <div class="ml-4 mr-4">
  299. <div class="flex items-center">1、点击下载 <el-link type="primary"
  300. @click="downloadFile('/upload/人员导入模板.xlsx', '人员导入模板.xlsx')">人员导入模板.xlsx</el-link></div>
  301. <div class="mt-4">2、填写excel模板,并上传</div>
  302. </div>
  303. </div>
  304. </el-dialog>
  305. <!-- 同步企业微信通讯录 -->
  306. <el-dialog v-model="dialogFrom.newSyncWithCorpWxDayloadVisable" width="600" :show-close="false"
  307. :before-close="handleClose">
  308. <template #header="{ close, titleId, titleClass }">
  309. <div class="flex justify-between items-center border-b pb-3 dialog-header">
  310. <h4 :id="titleId">同步企微通讯录</h4>
  311. <div class="flex">
  312. <el-button @click="dialogFrom.newSyncWithCorpWxDayloadVisable = false">取消</el-button>
  313. <el-button type="primary" :loading="loadingFrom.newSyncWithCorpWxDayloadLoading"
  314. @click="newSyncWithCorpWx()">
  315. 开始同步
  316. </el-button>
  317. </div>
  318. </div>
  319. </template>
  320. <div class="pt-4 px-12 py-2">
  321. 同步前请联系企业微信管理员检查应用授权的可见范围路径:管理企业-应用管理-工时管家-可见范围
  322. </div>
  323. </el-dialog>
  324. <!-- 公众号配置 -->
  325. <el-dialog v-model="dialogFrom.officialAccountSettingVisable" width="600" :show-close="false"
  326. :before-close="handleClose" :close="closeOfficialAccountInformation(officialAccountInformationRef)">
  327. <template #header="{ close, titleId, titleClass }">
  328. <div class="flex justify-between items-center border-b pb-3 dialog-header">
  329. <h4 :id="titleId">公众号配置</h4>
  330. <div class="flex">
  331. <el-button @click="dialogFrom.officialAccountSettingVisable = false">取消</el-button>
  332. <el-button type="primary" :loading="loadingFrom.officialAccountSettingLoading"
  333. @click="configureOfficialAccountInformation(officialAccountInformationRef)">
  334. 配置
  335. </el-button>
  336. </div>
  337. </div>
  338. </template>
  339. <div class="pt-4 px-12 py-2">
  340. <!-- <el-form ref="officialAccountInformationRef" :model="officialAccountInformationFrom"
  341. :rules="officialAccountInformationFromRules" label-width="auto" class="demo-ruleForm">
  342. <el-form-item label="名称:" prop="companyName">
  343. <el-input v-model="officialAccountInformationFrom.companyName" placeholder="请输入" />
  344. </el-form-item>
  345. <el-form-item label="appId:" prop="appId">
  346. <el-input v-model="officialAccountInformationFrom.appId" placeholder="请输入" />
  347. </el-form-item>
  348. <el-form-item label="appSecret:" prop="appSecret">
  349. <el-input v-model="officialAccountInformationFrom.appSecret" placeholder="请输入" />
  350. </el-form-item>
  351. </el-form> -->
  352. <div class="flex items-center mb-4">
  353. <div class="w-[80px] text-right mr-3">名称</div>
  354. <el-input v-model="officialAccountInformationFrom.companyName" placeholder="请输入" />
  355. </div>
  356. <div class="flex items-center mb-4">
  357. <div class="w-[80px] text-right mr-3">appId</div>
  358. <el-input v-model="officialAccountInformationFrom.appId" placeholder="请输入" />
  359. </div>
  360. <div class="flex items-center mb-4">
  361. <div class="w-[80px] text-right mr-3">appSecret</div>
  362. <el-input v-model="officialAccountInformationFrom.appSecret" placeholder="请输入" />
  363. </div>
  364. <el-text class="mx-1" type="warning">请联系客服, 获取ip白名单参数, 配置后方可生效</el-text>
  365. </div>
  366. </el-dialog>
  367. <!-- 新增人员 -->
  368. <AddPersonnelModal :data="{
  369. addPersonnelDialogVisible: dialogFrom.addPersonnelDialogVisible,
  370. deptList: deptListUntreated,
  371. roleList: roleList,
  372. personnelFromData: personnelFromData
  373. }" @closeModal="closeModal" @personnelModalConfirm="personnelModalConfirm" />
  374. <!-- 批量操作 -->
  375. <BatchOperation :batchData="visibleData" :batchNode="batchTableData" :visibleText="allText.batchText"
  376. :popup="visibleType" :batchOperationVisible="dialogFrom.batchOperationVisible" @close="closeModal" />
  377. <!-- 图片预览 -->
  378. <el-image-viewer v-if="showPreview" :url-list="previewSrcList" show-progress :initial-index="0"
  379. @close="showPreview = false" />
  380. </div>
  381. </template>
  382. <script lang="ts" setup>
  383. import { ref, reactive, onMounted, onBeforeMount, inject } from 'vue';
  384. import { UploadRequestOptions, dayjs, ElLoading, ElNotification } from 'element-plus'
  385. import { Search, CirclePlusFilled, Edit, CirclePlus, Delete, PictureFilled } from '@element-plus/icons-vue'
  386. import { FormInstance, FormRules, ElMessageBox } from 'element-plus'
  387. import { useStore } from '@/store/index'
  388. import { GET_DATA_LIST, DETELE_DEPT, MOD, GET_USERINFO, GET_ROUTELIST, DEACTIVEUSER, SETACTIVE, GET_DEPTLIST, BACTHSERROLE, GET_USERLIST, GET_ADDDEPT, ADD_USER, SETRESETPWD, EXPOERTUSER, EDIT_ADDDEPT, GET_COMPANY_WEI_XIN, SAVE_OR_UPDATE, ONE_CLICK_GENERATION, EXPORT_QR_CODE } from './api'
  389. import { post, uploadFile, downloadFileRequest } from "@/utils/request";
  390. import { getFromValue, updateDepTreeData, resetFromValue, confirmAction, downloadFile } from '@/utils/tools'
  391. import { storeToRefs } from 'pinia';
  392. import personnelSearch from '@/components/translationComponent/personnelSearch/personnelSearch.vue';
  393. // 导入页面
  394. import AddPersonnelModal from './module/AddPersonnelModal.vue'
  395. import BatchOperation from './module/BatchOperation.vue'
  396. import { formatDate } from '@/utils/times';
  397. import { URL_IMPORTTHREAD } from '../thread/constant';
  398. const { getFunctionList, getUserInfoVal } = useStore()
  399. const { userInfo } = storeToRefs(useStore());
  400. const globalPopup = inject<GlobalPopup>('globalPopup')
  401. // 定义类型
  402. interface deptRuleForm { // 部门表单类型
  403. name: string,
  404. id: string | number,
  405. parentId: string | number,
  406. managerId: string | number,
  407. otherManagerIds: string[] | number[],
  408. }
  409. interface officialAccountInformationInterface { // 公众号类型
  410. id?: string | number,
  411. companyName: string,
  412. appId: string,
  413. appSecret: string,
  414. }
  415. // 固定数据
  416. const stateOptions = [{ value: '3', label: '全部' }, { value: '1', label: '在职' }, { value: '0', label: '停用' }]
  417. // ref
  418. const importUserRef = ref<any>()
  419. // 定义变量
  420. const transitiondata = ref<any>() // 过度数据(针对二次弹窗)
  421. const pagePermission: any = ref([]) // 功能权限
  422. const loadingFrom = reactive({ // 所有加载状态
  423. tableLoading: false,
  424. deptDialogVisibleLoading: false,
  425. resignationLoading: false,
  426. exportUserLoading: false,
  427. importLoading: false,
  428. newSyncWithCorpWxDayloadLoading: false,
  429. officialAccountSettingLoading: false,
  430. })
  431. const dialogFrom: any = reactive({ // 所有弹窗状态
  432. addDeptDialogVisible: false,
  433. addPersonnelDialogVisible: false,
  434. batchOperationVisible: false,
  435. resignationVisible: false,
  436. exportUserVisible: false,
  437. importVisible: false,
  438. newSyncWithCorpWxDayloadVisable: false,
  439. officialAccountSettingVisable: false,
  440. });
  441. const allText = reactive({
  442. batchText: '批量操作'
  443. })
  444. const visibleType = ref<batchOperationType>('dept') // 弹窗类型
  445. const visibleData = ref<any>([]) // 批量弹窗数据源
  446. const totalTable = ref(0) // 表格总数
  447. const tableData: any = ref([]) // 表格数据
  448. const roleList: any = ref([]) // 角色列表
  449. const userList: any = ref([]) // 用户列表
  450. const deptList: any = ref([]) // 部门数据
  451. const batchTableData: any = ref([]) // 批量数据
  452. const multipleTableRef: any = ref()
  453. const deptListUntreated: any = ref([]) // 部门数据(未处理)
  454. const deptListItem: any = ref({}) // 选中的部门数据
  455. const personnelFromData = ref({}) // 人员表单数据
  456. const resignationDate = ref(formatDate(new Date())) // 员工离职日期
  457. const exportRadio = ref('1') // 导出人员列表
  458. const teamForm = reactive({ // 筛选条件表单
  459. matchingType: 0,
  460. keyword: '',
  461. status: '3',
  462. pageIndex: 1,
  463. pageSize: 20,
  464. roleId: '',
  465. onlyDirect: '',
  466. departmentId: '-1',
  467. });
  468. const deptRuleFormRef = ref<FormInstance>() // 表单实例
  469. const deptForm = reactive<deptRuleForm>({ // 部门表单
  470. name: '',
  471. id: '',
  472. parentId: '',
  473. managerId: '',
  474. otherManagerIds: [],
  475. })
  476. const treeProps = { // 部门树配置
  477. children: 'children',
  478. label: 'label',
  479. }
  480. const officialAccountInformation = ref<officialAccountInformationInterface>({
  481. id: '',
  482. companyName: '',
  483. appId: '',
  484. appSecret: '',
  485. })
  486. const officialAccountInformationFrom = reactive<officialAccountInformationInterface>({
  487. id: '',
  488. companyName: '',
  489. appId: '',
  490. appSecret: '',
  491. })
  492. const officialAccountInformationRef = ref<FormInstance>()
  493. const showPreview = ref(false)
  494. const previewSrcList = ref<string[]>([])
  495. // 定义校验规则
  496. const deptRules = reactive<FormRules<typeof deptForm>>({ // 部门表单校验规则
  497. name: [{ required: true, trigger: 'blur', message: '请输入部门名称' }]
  498. })
  499. const officialAccountInformationFromRules = reactive<FormRules<typeof officialAccountInformation>>({
  500. companyName: [{ required: true, trigger: 'blur', message: '请输入' }],
  501. appId: [{ required: true, trigger: 'blur', message: '请输入' }],
  502. appSecret: [{ required: true, trigger: 'blur', message: '请输入' }]
  503. })
  504. function viewQrCode(row: any) {
  505. const { wxImgUrlWithTicket = '' } = row
  506. previewSrcList.value = [wxImgUrlWithTicket]
  507. showPreview.value = true
  508. }
  509. function exportQrCode() {
  510. ElNotification({
  511. title: '提示',
  512. message: '二维码导出中...',
  513. type: 'info',
  514. showClose: false,
  515. duration: 0
  516. })
  517. downloadFileRequest(EXPORT_QR_CODE, {}).then((blob) => {
  518. const url = window.URL.createObjectURL(blob);
  519. const link = document.createElement('a');
  520. link.href = url;
  521. link.setAttribute('download', '员工销售二维码.zip'); // 根据需要设定文件名
  522. document.body.appendChild(link);
  523. link.click();
  524. link.remove();
  525. window.URL.revokeObjectURL(url);
  526. globalPopup?.showSuccess('下载中 请稍后')
  527. })
  528. }
  529. // 同步企业微信通讯录
  530. function newSyncWithCorpWx() {
  531. const loading = ElLoading.service({
  532. lock: true,
  533. text: 'Loading',
  534. background: 'rgba(0, 0, 0, 0.7)',
  535. })
  536. post(`/wxcorp/getCorpMembsFromPlatform`, { companyId: userInfo.value.companyId }).then(() => {
  537. globalPopup?.showSuccess('同步成功')
  538. dialogFrom.newSyncWithCorpWxDayloadVisable = false
  539. getRoleList()
  540. getUserList()
  541. getTableData()
  542. getDeptList()
  543. }).finally(() => {
  544. loading.close();
  545. })
  546. }
  547. // 定义方法
  548. async function importUser(param: UploadRequestOptions) {
  549. loadingFrom.importLoading = true
  550. const formData = new FormData();
  551. formData.append('file', param.file)
  552. const res = await uploadFile(URL_IMPORTTHREAD, formData).finally(() => {
  553. importUserRef.value.clearFiles()
  554. loadingFrom.importLoading = false
  555. })
  556. loadingFrom.importLoading = false
  557. if (res.code == 'ok') {
  558. globalPopup?.showSuccess('导入成功')
  559. closeModal('importVisible')
  560. getTableData()
  561. return
  562. }
  563. globalPopup?.showError(res.msg || '')
  564. }
  565. // 获取公众号配置信息
  566. function getCompanyWeiXin() {
  567. post(GET_COMPANY_WEI_XIN, {}).then((res) => {
  568. const { companyName = '', id = '', appId = '', appSecret = '' } = res.data
  569. officialAccountInformation.value = {
  570. id, companyName, appId, appSecret
  571. }
  572. })
  573. }
  574. function oneClickGenerationOfQrCode() {
  575. ElNotification({
  576. title: '提示',
  577. message: '生成二维码中...',
  578. type: 'info',
  579. showClose: false,
  580. duration: 0
  581. })
  582. post(ONE_CLICK_GENERATION, {}).then(() => {
  583. globalPopup?.showSuccess('生成成功')
  584. getTableData()
  585. })
  586. }
  587. async function configureOfficialAccountInformation(_formEl: FormInstance | undefined) {
  588. // if (!formEl) return
  589. // await formEl.validate((valid) => {
  590. // if (valid) {
  591. const { companyName, appId, appSecret } = officialAccountInformationFrom
  592. if(!companyName || !appId || !appSecret) {
  593. globalPopup?.showError('请填写完整')
  594. return
  595. }
  596. confirmAction('确定保存当前公众号的配置吗?', '公众号配置', 'warning').then(() => {
  597. loadingFrom.officialAccountSettingLoading = true
  598. post(SAVE_OR_UPDATE, getFromValue(officialAccountInformationFrom)).then(() => {
  599. globalPopup?.showSuccess('配置成功')
  600. closeModal('officialAccountSettingVisable')
  601. getCompanyWeiXin()
  602. }).finally(() => {
  603. loadingFrom.officialAccountSettingLoading = false
  604. })
  605. })
  606. // }
  607. // })
  608. }
  609. function closeOfficialAccountInformation(formEl: FormInstance | undefined) {
  610. if (!formEl) return
  611. formEl.resetFields()
  612. }
  613. function officialAccountSetting() {
  614. Object.assign(officialAccountInformationFrom, officialAccountInformation.value || {
  615. id: '',
  616. companyName: '',
  617. appId: '',
  618. appSecret: ''
  619. })
  620. dialogFrom.officialAccountSettingVisable = true
  621. }
  622. function exportUser() {
  623. loadingFrom.exportUserLoading = true
  624. post(EXPOERTUSER, { containInvalid: exportRadio.value }).then((res) => {
  625. downloadFile(`${res.data}`, '人员列表.xlsx')
  626. globalPopup?.showSuccess('导出成功')
  627. closeModal('exportUserVisible')
  628. }).finally(() => {
  629. loadingFrom.exportUserLoading = false
  630. })
  631. }
  632. function enableUser(row: any) {
  633. const id = row.id
  634. post(SETACTIVE, { id, isActive: 1 }).then(() => {
  635. globalPopup?.showSuccess('启用成功')
  636. getTableData()
  637. })
  638. }
  639. function resignation() {
  640. const id = transitiondata.value.id || ''
  641. loadingFrom.resignationLoading = true
  642. post(DEACTIVEUSER, { id, inactiveDate: resignationDate.value }).then(() => {
  643. globalPopup?.showSuccess('停用成功')
  644. getTableData()
  645. closeModal('resignationVisible')
  646. }).finally(() => {
  647. loadingFrom.resignationLoading = false
  648. })
  649. }
  650. function resetPwd(row: any) {
  651. const userId = row.id
  652. confirmAction(`确定要为${row.name}重置密码吗?`, '重置密码').then(() => {
  653. post(SETRESETPWD, { userId }).then(() => {
  654. globalPopup?.showSuccess('密码已重置为000000,请通知员工及时修改')
  655. }).catch((err) => {
  656. globalPopup?.showError(err.msg)
  657. })
  658. })
  659. }
  660. function batchEnableItem() {
  661. const userIds = batchTableData.value.map((item: any) => item.id)
  662. post(BACTHSERROLE, { ids: JSON.stringify(userIds), isActive: 1 }).then(() => {
  663. globalPopup?.showSuccess('操作成功')
  664. changeBatch(false)
  665. getTableData()
  666. }).catch((err) => {
  667. globalPopup?.showError(err.msg)
  668. })
  669. }
  670. function changeBatch(flag: boolean = true) {
  671. if (flag) {
  672. batchTableData.value = multipleTableRef.value && multipleTableRef.value.getSelectionRows()
  673. } else {
  674. batchTableData.value = []
  675. multipleTableRef.value && multipleTableRef.value.clearSelection()
  676. }
  677. }
  678. function addPersone(item: any) {
  679. if (!item) {
  680. personnelFromData.value = {}
  681. dialogFrom.addPersonnelDialogVisible = true
  682. return
  683. }
  684. post(GET_USERINFO, { userId: item.id }).then(res => {
  685. const { id, name, phone, jobNumber, roleId, departmentCascade, departmentId, inductionDate } = res.data
  686. let newData = {
  687. id, name, phone, jobNumber, roleId,
  688. // departmentId: departmentCascade && departmentCascade.split(',').map(Number).reverse(),
  689. departmentId: departmentId,
  690. inductionDate
  691. }
  692. personnelFromData.value = newData
  693. dialogFrom.addPersonnelDialogVisible = true
  694. })
  695. }
  696. async function personnelModalConfirm(data: any, modelType: string) {
  697. post(ADD_USER, { ...data }).then(res => {
  698. if (res.code != 'ok') {
  699. dialogFrom[modelType] = false
  700. globalPopup?.showError(res.msg)
  701. return
  702. }
  703. dialogFrom[modelType] = false
  704. globalPopup?.showSuccess('添加成功')
  705. getTableData()
  706. }).catch(_err => {
  707. dialogFrom[modelType] = false
  708. })
  709. }
  710. function createDepartment(formEl: FormInstance | undefined) {
  711. if (!formEl) return
  712. let data = getFromValue(deptForm)
  713. loadingFrom.deptDialogVisibleLoading = true
  714. EDIT_ADDDEPT
  715. post(data.id ? EDIT_ADDDEPT : GET_ADDDEPT, { ...deptForm, otherManagerIds: data.otherManagerIds && data.otherManagerIds.join(',') }).then(res => {
  716. if (res.code != 'ok') {
  717. loadingFrom.deptDialogVisibleLoading = false
  718. globalPopup?.showError(res.msg)
  719. return
  720. }
  721. loadingFrom.deptDialogVisibleLoading = false
  722. globalPopup?.showSuccess(data.id ? '修改成功' : '创建成功')
  723. getDeptList()
  724. dialogFrom.addDeptDialogVisible = false
  725. }).catch(_err => {
  726. loadingFrom.deptDialogVisibleLoading = false
  727. })
  728. }
  729. function updateDepartment(type: string) {
  730. if (!deptListItem.value.id || deptListItem.value.id <= 0) return
  731. const { id, label, parentId, managerId, otherManagerIds } = deptListItem.value
  732. console.log(deptListItem.value)
  733. let data = { id, name: label, parentId, managerId, otherManagerIds }
  734. Object.assign(deptForm, data)
  735. dialogFrom[type] = true
  736. }
  737. function treeNode(item: any) {
  738. deptListItem.value = item
  739. teamForm.departmentId = item.id
  740. getTableData()
  741. }
  742. function deteleDept(data: any) {
  743. console.log(data)
  744. ElMessageBox.confirm(
  745. `确定删除【${data.label}】部门吗?`, '',
  746. {
  747. confirmButtonText: '确定',
  748. cancelButtonText: '取消',
  749. type: 'warning',
  750. }
  751. )
  752. .then(() => {
  753. post(DETELE_DEPT, { id: data.id }).then(res => {
  754. if (res.code != 'ok') {
  755. globalPopup?.showError(res.msg)
  756. return
  757. }
  758. globalPopup?.showSuccess('删除成功')
  759. getDeptList()
  760. })
  761. })
  762. }
  763. function getTableData() {
  764. loadingFrom.tableLoading = true
  765. post(GET_DATA_LIST, { ...teamForm }).then(res => {
  766. if (res.code != 'ok') {
  767. loadingFrom.tableLoading = false
  768. globalPopup?.showError(res.msg)
  769. return
  770. }
  771. loadingFrom.tableLoading = false
  772. totalTable.value = res.data.total
  773. tableData.value = res.data.records
  774. }).catch(_err => {
  775. loadingFrom.tableLoading = false
  776. })
  777. }
  778. function getRoleList() {
  779. const companyId = getUserInfoVal('companyId') || ''
  780. post(GET_ROUTELIST, { companyId }).then(res => {
  781. if (res.code != 'ok') {
  782. globalPopup?.showError(res.msg)
  783. return
  784. }
  785. roleList.value = res.data
  786. })
  787. }
  788. function getDeptList() {
  789. post(GET_DEPTLIST, {}).then(res => {
  790. if (res.code != 'ok') {
  791. globalPopup?.showError(res.msg)
  792. return
  793. }
  794. deptListUntreated.value = updateDepTreeData(res.data, false)
  795. deptList.value = updateDepTreeData(res.data, true)
  796. })
  797. }
  798. function getUserList() {
  799. post(GET_USERLIST, {}).then(res => {
  800. if (res.code != 'ok') {
  801. globalPopup?.showError(res.msg)
  802. return
  803. }
  804. userList.value = res.data
  805. })
  806. }
  807. function mouseleave(data: any, $event: Event) {
  808. if (data.id <= 0) return;
  809. const target = $event.currentTarget as HTMLElement;
  810. const iconElement = target.querySelector('#treeIcon');
  811. if (iconElement) {
  812. iconElement.setAttribute("class", "treeIcon nodeEle");
  813. }
  814. }
  815. function mouseover(data: any, $event: MouseEvent) {
  816. if (data.id <= 0) return;
  817. const target = $event.currentTarget as HTMLElement;
  818. const iconElement = target.querySelector('#treeIcon');
  819. if (iconElement) {
  820. iconElement.setAttribute("class", "treeIcon");
  821. }
  822. }
  823. function dialogFromCli(type: string, data: any = {}, flag: boolean = false) {
  824. resetDialog()
  825. if (flag) {
  826. const { id } = data
  827. deptForm.parentId = id
  828. }
  829. dialogFrom[type] = true
  830. }
  831. function handleSizeChange(val: number) {
  832. teamForm.pageIndex = 1
  833. teamForm.pageSize = val
  834. getTableData()
  835. }
  836. function handleCurrentChange(val: number) {
  837. teamForm.pageIndex = val
  838. getTableData()
  839. }
  840. function resetDialog() {
  841. let newDeptForm = resetFromValue(deptForm)
  842. Object.assign(deptForm, newDeptForm)
  843. }
  844. function batchItem(text: string, type: batchOperationType, data: any) {
  845. allText.batchText = text,
  846. visibleType.value = type
  847. visibleData.value = data
  848. dialogFrom.batchOperationVisible = true
  849. }
  850. function handleClose(done: any) {
  851. done()
  852. }
  853. function transitionOperation(type: string, data: any) {
  854. if (type == 'disable') { // 停用
  855. resignationDate.value = formatDate(new Date())
  856. console.log(resignationDate.value, '<==== 离职')
  857. dialogFrom.resignationVisible = true
  858. } else if (type == 'exportUser') { // 导出
  859. exportRadio.value = '1'
  860. dialogFrom.exportUserVisible = true
  861. } else if (type == 'importUser') { // 导入
  862. dialogFrom.importVisible = true
  863. }
  864. transitiondata.value = data
  865. }
  866. function closeModal(modelType: string, flag: boolean = false) {
  867. dialogFrom[modelType] = false
  868. if (flag) {
  869. changeBatch(false)
  870. getTableData()
  871. }
  872. }
  873. onBeforeMount(() => {
  874. pagePermission.value = getFunctionList(MOD)
  875. })
  876. onMounted(() => {
  877. getRoleList()
  878. getUserList()
  879. getTableData()
  880. getDeptList()
  881. getCompanyWeiXin()
  882. });
  883. </script>
  884. <style lang="scss" scoped>
  885. .teamstyle {
  886. .team-header {
  887. padding: 0.75rem 1.25rem;
  888. box-sizing: border-box
  889. }
  890. .textFont {
  891. font-size: 16px;
  892. }
  893. .textSpan {
  894. color: $fontGray;
  895. }
  896. }
  897. .const-left {
  898. padding: 0.75rem 0;
  899. .treeContent {
  900. width: 87%;
  901. .custom-tree-node {
  902. display: flex;
  903. align-items: center;
  904. justify-content: space-between;
  905. width: 100%;
  906. }
  907. .treeLabel {
  908. width: 80%;
  909. white-space: nowrap;
  910. overflow: hidden;
  911. text-overflow: ellipsis;
  912. }
  913. .treeIcon {
  914. display: flex;
  915. align-items: center;
  916. justify-self: flex-end;
  917. }
  918. .nodeEle {
  919. display: none;
  920. }
  921. }
  922. }
  923. .operation {
  924. cursor: pointer;
  925. }
  926. </style>
  927. <style>
  928. .temaClass .el-tree-node {
  929. padding: 4px 0;
  930. }
  931. </style>