index.vue 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876
  1. <template>
  2. <div class="p-2">
  3. <el-row :gutter="20">
  4. <!-- 部门树 -->
  5. <el-col :lg="4" :xs="24" style="">
  6. <el-card shadow="hover">
  7. <el-input v-model="deptName" :placeholder="t('user.search.deptPlaceholder')" prefix-icon="Search" clearable />
  8. <el-tree
  9. ref="deptTreeRef"
  10. class="mt-2"
  11. node-key="id"
  12. :data="deptOptions"
  13. :props="{ label: 'label', children: 'children' } as any"
  14. :expand-on-click-node="false"
  15. :filter-node-method="filterNode"
  16. highlight-current
  17. default-expand-all
  18. @node-click="handleNodeClick"
  19. />
  20. </el-card>
  21. </el-col>
  22. <el-col :lg="20" :xs="24">
  23. <transition :enter-active-class="proxy?.animate.searchAnimate.enter" :leave-active-class="proxy?.animate.searchAnimate.leave">
  24. <div v-show="showSearch" class="mb-[10px]">
  25. <el-card shadow="hover">
  26. <el-form ref="queryFormRef" :model="queryParams" :inline="true" label-width="140px">
  27. <el-form-item :label="t('user.search.userName')" prop="userName">
  28. <el-input v-model="queryParams.userName" :placeholder="t('user.search.userNamePlaceholder')" clearable @keyup.enter="handleQuery" />
  29. </el-form-item>
  30. <el-form-item :label="t('user.search.nickName')" prop="nickName">
  31. <el-input v-model="queryParams.nickName" :placeholder="t('user.search.nickNamePlaceholder')" clearable @keyup.enter="handleQuery" />
  32. </el-form-item>
  33. <el-form-item :label="t('user.search.phonenumber')" prop="phonenumber">
  34. <el-input
  35. v-model="queryParams.phonenumber"
  36. :placeholder="t('user.search.phonenumberPlaceholder')"
  37. clearable
  38. @keyup.enter="handleQuery"
  39. />
  40. </el-form-item>
  41. <el-form-item :label="t('user.search.status')" prop="status">
  42. <el-select v-model="queryParams.status" :placeholder="t('user.search.statusPlaceholder')" clearable>
  43. <el-option v-for="dict in sys_normal_disable" :key="dict.value" :label="parseI18nName(dict.label)" :value="dict.value" />
  44. </el-select>
  45. </el-form-item>
  46. <el-form-item :label="t('user.search.createTime')" style="width: 380px">
  47. <el-date-picker
  48. v-model="dateRange"
  49. value-format="YYYY-MM-DD HH:mm:ss"
  50. type="daterange"
  51. range-separator="-"
  52. :start-placeholder="t('user.search.startDate')"
  53. :end-placeholder="t('user.search.endDate')"
  54. :default-time="[new Date(2000, 1, 1, 0, 0, 0), new Date(2000, 1, 1, 23, 59, 59)]"
  55. ></el-date-picker>
  56. </el-form-item>
  57. <el-form-item>
  58. <el-button type="primary" icon="Search" @click="handleQuery">{{ t('user.search.search') }}</el-button>
  59. <el-button icon="Refresh" @click="resetQuery">{{ t('user.search.reset') }}</el-button>
  60. </el-form-item>
  61. </el-form>
  62. </el-card>
  63. </div>
  64. </transition>
  65. <el-card shadow="hover">
  66. <template #header>
  67. <el-row :gutter="10">
  68. <el-col :span="1.5">
  69. <el-button v-has-permi="['system:user:add']" type="primary" plain icon="Plus" @click="handleAdd()">{{
  70. t('user.button.add')
  71. }}</el-button>
  72. </el-col>
  73. <el-col :span="1.5">
  74. <el-button v-has-permi="['system:user:edit']" type="success" plain :disabled="single" icon="Edit" @click="handleUpdate()">
  75. {{ t('user.button.edit') }}
  76. </el-button>
  77. </el-col>
  78. <el-col :span="1.5">
  79. <el-button v-has-permi="['system:user:remove']" type="danger" plain :disabled="multiple" icon="Delete" @click="handleDelete()">
  80. {{ t('user.button.delete') }}
  81. </el-button>
  82. </el-col>
  83. <el-col :span="1.5">
  84. <el-dropdown class="mt-[1px]">
  85. <el-button plain type="info">
  86. {{ t('user.button.more') }}
  87. <el-icon class="el-icon--right"><arrow-down /></el-icon
  88. ></el-button>
  89. <template #dropdown>
  90. <el-dropdown-menu>
  91. <el-dropdown-item icon="Download" @click="importTemplate">{{ t('user.button.downloadTemplate') }}</el-dropdown-item>
  92. <!-- 注意 由于el-dropdown-item标签是延迟加载的 所以v-has-permi自定义标签不生效 需要使用v-if调用方法执行 -->
  93. <el-dropdown-item v-if="checkPermi(['system:user:import'])" icon="Top" @click="handleImport">{{
  94. t('user.button.import')
  95. }}</el-dropdown-item>
  96. <el-dropdown-item v-if="checkPermi(['system:user:export'])" icon="Download" @click="handleExport">{{
  97. t('user.button.export')
  98. }}</el-dropdown-item>
  99. </el-dropdown-menu>
  100. </template>
  101. </el-dropdown>
  102. </el-col>
  103. <right-toolbar v-model:show-search="showSearch" :columns="columns" :search="true" @query-table="getList"></right-toolbar>
  104. </el-row>
  105. </template>
  106. <el-table v-loading="loading" border :data="userList" style="width: 100%" @selection-change="handleSelectionChange">
  107. <el-table-column type="selection" width="50" align="center" />
  108. <el-table-column v-if="columns[0].visible" key="userId" :label="t('user.table.userId')" align="center" prop="userId" />
  109. <el-table-column
  110. v-if="columns[1].visible"
  111. key="userName"
  112. :label="t('user.table.userName')"
  113. align="center"
  114. prop="userName"
  115. :show-overflow-tooltip="true"
  116. />
  117. <el-table-column
  118. v-if="columns[2].visible"
  119. key="nickName"
  120. :label="t('user.table.nickName')"
  121. align="center"
  122. prop="nickName"
  123. :show-overflow-tooltip="true"
  124. />
  125. <el-table-column
  126. v-if="columns[3].visible"
  127. key="deptName"
  128. :label="t('user.table.deptName')"
  129. align="center"
  130. prop="deptName"
  131. :show-overflow-tooltip="true"
  132. />
  133. <el-table-column
  134. v-if="columns[4].visible"
  135. key="phonenumber"
  136. :label="t('user.table.phonenumber')"
  137. align="center"
  138. prop="phonenumber"
  139. width="120"
  140. />
  141. <el-table-column v-if="columns[5].visible" key="status" :label="t('user.table.status')" align="center">
  142. <template #default="scope">
  143. <el-switch v-model="scope.row.status" active-value="0" inactive-value="1" @change="handleStatusChange(scope.row)"></el-switch>
  144. </template>
  145. </el-table-column>
  146. <el-table-column v-if="columns[6].visible" key="appletStatus" :label="t('user.table.appletStatus')" align="center">
  147. <template #default="scope">
  148. <el-switch
  149. v-hasPermi="['system:user:edit']"
  150. v-model="scope.row.appletStatus"
  151. :active-value="0"
  152. :inactive-value="1"
  153. @change="handleAppletStatusChange(scope.row)"
  154. ></el-switch>
  155. </template>
  156. </el-table-column>
  157. <el-table-column v-if="columns[7].visible" :label="t('user.table.createTime')" align="center" prop="createTime" width="160">
  158. <template #default="scope">
  159. <span>{{ scope.row.createTime }}</span>
  160. </template>
  161. </el-table-column>
  162. <el-table-column :label="t('user.table.operation')" fixed="right" width="350" class-name="small-padding fixed-width">
  163. <template #default="scope">
  164. <el-button
  165. v-if="scope.row.userId !== 1"
  166. v-hasPermi="['system:user:edit']"
  167. type="primary"
  168. icon="Edit"
  169. style="padding: 0 5px; font-size: 10px; height: 24px"
  170. @click="handleUpdate(scope.row)"
  171. >
  172. {{ t('user.button.edit') }}
  173. </el-button>
  174. <el-button
  175. v-if="scope.row.userId !== 1"
  176. v-hasPermi="['system:user:remove']"
  177. type="danger"
  178. icon="Delete"
  179. style="padding: 0 5px; font-size: 10px; height: 24px"
  180. @click="handleDelete(scope.row)"
  181. >
  182. {{ t('user.button.delete') }}
  183. </el-button>
  184. <el-button
  185. v-if="scope.row.userId !== 1"
  186. v-hasPermi="['system:user:resetPwd']"
  187. type="warning"
  188. icon="Key"
  189. style="padding: 0 5px; font-size: 10px; height: 24px"
  190. @click="handleResetPwd(scope.row)"
  191. >
  192. {{ t('user.button.resetPwd') }}
  193. </el-button>
  194. <el-button
  195. v-if="scope.row.userId !== 1"
  196. v-hasPermi="['system:user:edit']"
  197. type="success"
  198. icon="CircleCheck"
  199. style="padding: 0 5px; font-size: 10px; height: 24px"
  200. @click="handleAuthRole(scope.row)"
  201. >
  202. {{ t('user.button.assignRole') }}
  203. </el-button>
  204. </template>
  205. </el-table-column>
  206. </el-table>
  207. <pagination
  208. v-show="total > 0"
  209. v-model:page="queryParams.pageNum"
  210. v-model:limit="queryParams.pageSize"
  211. :total="total"
  212. @pagination="getList"
  213. />
  214. </el-card>
  215. </el-col>
  216. </el-row>
  217. <!-- 添加或修改用户配置对话框 -->
  218. <el-dialog ref="formDialogRef" v-model="dialog.visible" :title="dialog.title" width="900px" append-to-body @close="closeDialog">
  219. <el-form ref="userFormRef" :model="form" :rules="rules" label-width="180px">
  220. <el-row>
  221. <el-col :span="12">
  222. <el-form-item :label="t('user.form.nickName')" prop="nickName">
  223. <el-input v-model="form.nickName" :placeholder="t('user.form.nickNamePlaceholder')" maxlength="30" />
  224. </el-form-item>
  225. </el-col>
  226. <el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
  227. <el-form-item :label="t('user.form.deptId')" prop="deptId">
  228. <el-tree-select
  229. v-model="form.deptId"
  230. :data="enabledDeptOptions"
  231. :props="{ value: 'id', label: 'label', children: 'children' } as any"
  232. value-key="id"
  233. :placeholder="t('user.form.deptIdPlaceholder')"
  234. check-strictly
  235. @change="handleDeptChange"
  236. />
  237. </el-form-item>
  238. </el-col>
  239. </el-row>
  240. <el-row>
  241. <el-col :span="12">
  242. <el-form-item :label="t('user.form.phonenumber')" prop="phonenumber">
  243. <el-input v-model="form.phonenumber" :placeholder="t('user.form.phonenumberPlaceholder')" maxlength="11" />
  244. </el-form-item>
  245. </el-col>
  246. <el-col :span="12">
  247. <el-form-item :label="t('user.form.email')" prop="email">
  248. <el-input v-model="form.email" :placeholder="t('user.form.emailPlaceholder')" maxlength="50" />
  249. </el-form-item>
  250. </el-col>
  251. </el-row>
  252. <el-row>
  253. <el-col :span="12">
  254. <el-form-item v-if="form.userId == undefined" :label="t('user.form.userName')" prop="userName">
  255. <el-input v-model="form.userName" :placeholder="t('user.form.userNamePlaceholder')" maxlength="30" />
  256. </el-form-item>
  257. </el-col>
  258. <el-col :span="12">
  259. <el-form-item v-if="form.userId == undefined" :label="t('user.form.password')" prop="password">
  260. <el-input v-model="form.password" :placeholder="t('user.form.passwordPlaceholder')" type="password" maxlength="20" show-password />
  261. </el-form-item>
  262. </el-col>
  263. </el-row>
  264. <el-row>
  265. <el-col :span="12">
  266. <el-form-item :label="t('user.form.sex')">
  267. <el-select v-model="form.sex" :placeholder="t('user.form.selectPlaceholder')">
  268. <el-option v-for="dict in sys_user_sex" :key="dict.value" :label="parseI18nName(dict.label)" :value="dict.value"></el-option>
  269. </el-select>
  270. </el-form-item>
  271. </el-col>
  272. <el-col :span="12">
  273. <el-form-item :label="t('user.form.status')">
  274. <el-radio-group v-model="form.status">
  275. <el-radio v-for="dict in sys_normal_disable" :key="dict.value" :value="dict.value">{{ parseI18nName(dict.label) }}</el-radio>
  276. </el-radio-group>
  277. </el-form-item>
  278. </el-col>
  279. </el-row>
  280. <el-row>
  281. <el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
  282. <el-form-item :label="t('user.form.postIds')">
  283. <el-select v-model="form.postIds" multiple :placeholder="t('user.form.postIdsPlaceholder')">
  284. <el-option
  285. v-for="item in postOptions"
  286. :key="item.postId"
  287. :label="item.postName"
  288. :value="item.postId"
  289. :disabled="item.status == '1'"
  290. ></el-option>
  291. </el-select>
  292. </el-form-item>
  293. </el-col>
  294. <el-col :span="12" v-if="form.userId == null || form.userId != useUserStore().userId">
  295. <el-form-item :label="t('user.form.roleIds')" prop="roleIds">
  296. <el-select v-model="form.roleIds" filterable multiple :placeholder="t('user.form.roleIdsPlaceholder')">
  297. <el-option
  298. v-for="item in roleOptions"
  299. :key="item.roleId"
  300. :label="item.roleName"
  301. :value="item.roleId"
  302. :disabled="item.status == '1'"
  303. ></el-option>
  304. </el-select>
  305. </el-form-item>
  306. </el-col>
  307. </el-row>
  308. <el-row>
  309. <el-col :span="24">
  310. <el-form-item label="项目">
  311. <div style="width: 100%">
  312. <el-checkbox v-model="isAllProjectsSelected" @change="handleSelectAllProjects" style="margin-bottom: 10px"> 全选 </el-checkbox>
  313. <el-checkbox-group
  314. v-model="selectedProjectIds"
  315. @change="handleProjectChange"
  316. :disabled="isAllProjectsSelected"
  317. style="width: 100%; max-height: 300px; overflow-y: auto; display: flex; flex-direction: column"
  318. >
  319. <el-checkbox v-for="item in projectOptions" :key="item.id" :label="item.id" style="margin: 5px 0">
  320. {{ item.name }}
  321. </el-checkbox>
  322. </el-checkbox-group>
  323. </div>
  324. </el-form-item>
  325. </el-col>
  326. </el-row>
  327. <el-row>
  328. <el-col :span="24">
  329. <el-form-item :label="t('user.form.remark')">
  330. <el-input v-model="form.remark" type="textarea" :placeholder="t('user.form.remarkPlaceholder')"></el-input>
  331. </el-form-item>
  332. </el-col>
  333. </el-row>
  334. </el-form>
  335. <template #footer>
  336. <div class="dialog-footer">
  337. <el-button type="primary" @click="submitForm">{{ t('user.button.submit') }}</el-button>
  338. <el-button @click="cancel()">{{ t('user.button.cancel') }}</el-button>
  339. </div>
  340. </template>
  341. </el-dialog>
  342. <!-- 用户导入对话框 -->
  343. <el-dialog v-model="upload.open" :title="upload.title" width="550px" append-to-body>
  344. <el-upload
  345. ref="uploadRef"
  346. :limit="1"
  347. accept=".xlsx, .xls"
  348. :headers="upload.headers"
  349. :action="upload.url + '?updateSupport=' + upload.updateSupport"
  350. :disabled="upload.isUploading"
  351. :on-progress="handleFileUploadProgress"
  352. :on-success="handleFileSuccess"
  353. :auto-upload="false"
  354. drag
  355. >
  356. <el-icon class="el-icon--upload">
  357. <i-ep-upload-filled />
  358. </el-icon>
  359. <div class="el-upload__text" v-html="t('user.upload.dragText')"></div>
  360. <template #tip>
  361. <div class="text-center el-upload__tip">
  362. <div class="el-upload__tip"><el-checkbox v-model="upload.updateSupport" />{{ t('user.upload.updateSupport') }}</div>
  363. <span>{{ t('user.upload.fileType') }}</span>
  364. <el-link type="primary" :underline="false" style="font-size: 12px; vertical-align: baseline" @click="importTemplate">{{
  365. t('user.upload.downloadTemplate')
  366. }}</el-link>
  367. </div>
  368. </template>
  369. </el-upload>
  370. <template #footer>
  371. <div class="dialog-footer">
  372. <el-button type="primary" @click="submitFileForm">{{ t('user.button.submit') }}</el-button>
  373. <el-button @click="upload.open = false">{{ t('user.button.cancel') }}</el-button>
  374. </div>
  375. </template>
  376. </el-dialog>
  377. </div>
  378. </template>
  379. <script setup name="User" lang="ts">
  380. import api from '@/api/system/user';
  381. import { UserForm, UserQuery, UserVO } from '@/api/system/user/types';
  382. import { DeptTreeVO, DeptVO } from '@/api/system/dept/types';
  383. import { RoleVO } from '@/api/system/role/types';
  384. import { PostVO } from '@/api/system/post/types';
  385. import { globalHeaders } from '@/utils/request';
  386. import { to } from 'await-to-js';
  387. import { optionselect } from '@/api/system/post';
  388. import { checkPermi } from '@/utils/permission';
  389. import { useUserStore } from '@/store/modules/user';
  390. import { useI18n } from 'vue-i18n';
  391. import { parseI18nName } from '@/utils/i18n';
  392. import request from '@/utils/request';
  393. const router = useRouter();
  394. const { proxy } = getCurrentInstance() as ComponentInternalInstance;
  395. const { t } = useI18n();
  396. const { sys_normal_disable, sys_user_sex } = toRefs<any>(proxy?.useDict('sys_normal_disable', 'sys_user_sex'));
  397. const userList = ref<UserVO[]>();
  398. const loading = ref(true);
  399. const showSearch = ref(true);
  400. const ids = ref<Array<number | string>>([]);
  401. const single = ref(true);
  402. const multiple = ref(true);
  403. const total = ref(0);
  404. const dateRange = ref<[DateModelType, DateModelType]>(['', '']);
  405. const deptName = ref('');
  406. const deptOptions = ref<DeptTreeVO[]>([]);
  407. const enabledDeptOptions = ref<DeptTreeVO[]>([]);
  408. const initPassword = ref<string>('');
  409. const postOptions = ref<PostVO[]>([]);
  410. const roleOptions = ref<RoleVO[]>([]);
  411. const projectOptions = ref<Array<{ id: number; name: string }>>([]);
  412. const selectedProjectIds = ref<number[]>([]);
  413. const isAllProjectsSelected = ref(false);
  414. /*** 用户导入参数 */
  415. const upload = reactive<ImportOption>({
  416. // 是否显示弹出层(用户导入)
  417. open: false,
  418. // 弹出层标题(用户导入)
  419. title: '',
  420. // 是否禁用上传
  421. isUploading: false,
  422. // 是否更新已经存在的用户数据
  423. updateSupport: 0,
  424. // 设置上传的请求头部
  425. headers: globalHeaders(),
  426. // 上传的地址
  427. url: import.meta.env.VITE_APP_BASE_API + '/system/user/importData'
  428. });
  429. // 列显隐信息
  430. const columns = ref<FieldOption[]>([
  431. { key: 0, label: t('user.table.userId'), visible: false, children: [] },
  432. { key: 1, label: t('user.table.userName'), visible: true, children: [] },
  433. { key: 2, label: t('user.table.nickName'), visible: true, children: [] },
  434. { key: 3, label: t('user.table.deptName'), visible: true, children: [] },
  435. { key: 4, label: t('user.table.phonenumber'), visible: true, children: [] },
  436. { key: 5, label: t('user.table.status'), visible: true, children: [] },
  437. { key: 6, label: t('user.table.appletStatus'), visible: true, children: [] },
  438. { key: 7, label: t('user.table.createTime'), visible: true, children: [] }
  439. ]);
  440. const deptTreeRef = ref<ElTreeInstance>();
  441. const queryFormRef = ref<ElFormInstance>();
  442. const userFormRef = ref<ElFormInstance>();
  443. const uploadRef = ref<ElUploadInstance>();
  444. const formDialogRef = ref<ElDialogInstance>();
  445. const dialog = reactive<DialogOption>({
  446. visible: false,
  447. title: ''
  448. });
  449. const initFormData: UserForm = {
  450. userId: undefined,
  451. deptId: undefined,
  452. userName: '',
  453. nickName: undefined,
  454. password: '',
  455. phonenumber: undefined,
  456. email: undefined,
  457. sex: undefined,
  458. status: '0',
  459. remark: '',
  460. postIds: [],
  461. roleIds: [],
  462. projects: ''
  463. };
  464. const initData: PageData<UserForm, UserQuery> = {
  465. form: { ...initFormData },
  466. queryParams: {
  467. pageNum: 1,
  468. pageSize: 10,
  469. userName: '',
  470. phonenumber: '',
  471. status: '',
  472. deptId: '',
  473. roleId: ''
  474. },
  475. rules: {
  476. userName: [
  477. { required: true, message: t('user.rule.userNameRequired'), trigger: 'blur' },
  478. {
  479. min: 2,
  480. max: 20,
  481. message: t('user.rule.userNameLength'),
  482. trigger: 'blur'
  483. }
  484. ],
  485. nickName: [{ required: true, message: t('user.rule.nickNameRequired'), trigger: 'blur' }],
  486. password: [
  487. { required: true, message: t('user.rule.passwordRequired'), trigger: 'blur' },
  488. {
  489. min: 5,
  490. max: 20,
  491. message: t('user.rule.passwordLength'),
  492. trigger: 'blur'
  493. },
  494. { pattern: /^[^<>"'|\\]+$/, message: t('user.rule.passwordPattern'), trigger: 'blur' }
  495. ],
  496. email: [
  497. {
  498. type: 'email',
  499. message: t('user.rule.emailFormat'),
  500. trigger: ['blur', 'change']
  501. }
  502. ],
  503. phonenumber: [
  504. {
  505. pattern: /^1[3456789][0-9]\d{8}$/,
  506. message: t('user.rule.phonenumberFormat'),
  507. trigger: 'blur'
  508. }
  509. ],
  510. roleIds: [{ required: true, message: t('user.rule.roleIdsRequired'), trigger: 'blur' }]
  511. }
  512. };
  513. const data = reactive<PageData<UserForm, UserQuery>>(initData);
  514. const { queryParams, form, rules } = toRefs<PageData<UserForm, UserQuery>>(data);
  515. /** 通过条件过滤节点 */
  516. const filterNode = (value: string, data: any) => {
  517. if (!value) return true;
  518. return data.label.indexOf(value) !== -1;
  519. };
  520. /** 根据名称筛选部门树 */
  521. watchEffect(
  522. () => {
  523. deptTreeRef.value?.filter(deptName.value);
  524. },
  525. {
  526. flush: 'post' // watchEffect会在DOM挂载或者更新之前就会触发,此属性控制在DOM元素更新后运行
  527. }
  528. );
  529. /** 查询用户列表 */
  530. const getList = async () => {
  531. loading.value = true;
  532. const res = await api.listUser(proxy?.addDateRange(queryParams.value, dateRange.value));
  533. loading.value = false;
  534. userList.value = res.rows;
  535. total.value = res.total;
  536. };
  537. /** 查询部门下拉树结构 */
  538. const getDeptTree = async () => {
  539. const res = await api.deptTreeSelect();
  540. deptOptions.value = res.data;
  541. enabledDeptOptions.value = filterDisabledDept(res.data);
  542. };
  543. /** 过滤禁用的部门 */
  544. const filterDisabledDept = (deptList: DeptTreeVO[]) => {
  545. return deptList.filter((dept) => {
  546. if (dept.disabled) {
  547. return false;
  548. }
  549. if (dept.children && dept.children.length) {
  550. dept.children = filterDisabledDept(dept.children);
  551. }
  552. return true;
  553. });
  554. };
  555. /** 获取项目列表 */
  556. const getProjectList = async () => {
  557. try {
  558. const response = await request({
  559. url: '/project/management/listOnUser',
  560. method: 'get'
  561. });
  562. if (response.code === 200) {
  563. projectOptions.value = response.data || [];
  564. }
  565. } catch (error) {
  566. console.error('获取项目列表失败:', error);
  567. projectOptions.value = [];
  568. }
  569. };
  570. /** 处理全选项目 */
  571. const handleSelectAllProjects = (checked: boolean) => {
  572. // 无论选中还是取消全选,都清空手动选择的项目
  573. selectedProjectIds.value = [];
  574. };
  575. /** 处理项目选择变化 */
  576. const handleProjectChange = (value: number[]) => {
  577. // 手动选择项目时,取消全选状态
  578. if (value.length > 0) {
  579. isAllProjectsSelected.value = false;
  580. }
  581. };
  582. /** 监听项目选择变化,更新 form.value.projects */
  583. watch(
  584. [isAllProjectsSelected, selectedProjectIds],
  585. () => {
  586. if (isAllProjectsSelected.value) {
  587. form.value.projects = '*';
  588. } else if (selectedProjectIds.value.length > 0) {
  589. form.value.projects = selectedProjectIds.value.join(',');
  590. } else {
  591. form.value.projects = '';
  592. }
  593. },
  594. { deep: true }
  595. );
  596. /** 节点单击事件 */
  597. const handleNodeClick = (data: DeptVO) => {
  598. queryParams.value.deptId = data.id;
  599. handleQuery();
  600. };
  601. /** 搜索按钮操作 */
  602. const handleQuery = () => {
  603. queryParams.value.pageNum = 1;
  604. getList();
  605. };
  606. /** 重置按钮操作 */
  607. const resetQuery = () => {
  608. dateRange.value = ['', ''];
  609. queryFormRef.value?.resetFields();
  610. queryParams.value.pageNum = 1;
  611. queryParams.value.deptId = undefined;
  612. deptTreeRef.value?.setCurrentKey(undefined);
  613. handleQuery();
  614. };
  615. /** 删除按钮操作 */
  616. const handleDelete = async (row?: UserVO) => {
  617. const userIds = row?.userId || ids.value;
  618. const [err] = await to(proxy?.$modal.confirm(t('user.message.deleteConfirm', { ids: userIds })) as any);
  619. if (!err) {
  620. await api.delUser(userIds);
  621. await getList();
  622. proxy?.$modal.msgSuccess(t('user.message.deleteSuccess'));
  623. }
  624. };
  625. /** 用户状态修改 */
  626. const handleStatusChange = async (row: UserVO) => {
  627. const text = row.status === '0' ? t('user.message.statusEnable') : t('user.message.statusDisable');
  628. try {
  629. await proxy?.$modal.confirm(t('user.message.statusChangeConfirm', { text, userName: row.userName }));
  630. await api.changeUserStatus(row.userId, row.status);
  631. proxy?.$modal.msgSuccess(t('user.message.statusChangeSuccess', { text }));
  632. } catch (err) {
  633. row.status = row.status === '0' ? '1' : '0';
  634. }
  635. };
  636. /** 小程序状态修改 */
  637. const handleAppletStatusChange = async (row: UserVO) => {
  638. const text = row.appletStatus === 0 ? t('user.message.appletStatusEnable') : t('user.message.appletStatusDisable');
  639. try {
  640. await proxy?.$modal.confirm(t('user.message.appletStatusChangeConfirm', { text, userName: row.userName }));
  641. await api.changeUserAppletStatus(row.userId, row.appletStatus);
  642. proxy?.$modal.msgSuccess(t('user.message.appletStatusChangeSuccess', { text }));
  643. } catch (err) {
  644. row.appletStatus = row.appletStatus === 0 ? 1 : 0;
  645. }
  646. };
  647. /** 跳转角色分配 */
  648. const handleAuthRole = (row: UserVO) => {
  649. const userId = row.userId;
  650. router.push('/system/user-auth/role/' + userId);
  651. };
  652. /** 重置密码按钮操作 */
  653. const handleResetPwd = async (row: UserVO) => {
  654. const [err, res] = await to(
  655. ElMessageBox.prompt(t('user.message.resetPwdText', { userName: row.userName }), t('user.message.resetPwdTitle'), {
  656. confirmButtonText: t('user.message.confirmButton'),
  657. cancelButtonText: t('user.message.cancelButton'),
  658. closeOnClickModal: false,
  659. inputPattern: /^.{5,20}$/,
  660. inputErrorMessage: t('user.message.resetPwdLengthError'),
  661. inputValidator: (value) => {
  662. if (/<|>|"|'|\||\\/.test(value)) {
  663. return t('user.message.resetPwdPatternError');
  664. }
  665. }
  666. })
  667. );
  668. if (!err && res) {
  669. await api.resetUserPwd(row.userId, res.value);
  670. proxy?.$modal.msgSuccess(t('user.message.resetPwdSuccess', { password: res.value }));
  671. }
  672. };
  673. /** 选择条数 */
  674. const handleSelectionChange = (selection: UserVO[]) => {
  675. ids.value = selection.map((item) => item.userId);
  676. single.value = selection.length != 1;
  677. multiple.value = !selection.length;
  678. };
  679. /** 导入按钮操作 */
  680. const handleImport = () => {
  681. upload.title = t('user.dialog.import');
  682. upload.open = true;
  683. };
  684. /** 导出按钮操作 */
  685. const handleExport = () => {
  686. proxy?.download(
  687. 'system/user/export',
  688. {
  689. ...queryParams.value
  690. },
  691. `user_${new Date().getTime()}.xlsx`
  692. );
  693. };
  694. /** 下载模板操作 */
  695. const importTemplate = () => {
  696. proxy?.download('system/user/importTemplate', {}, `user_template_${new Date().getTime()}.xlsx`);
  697. };
  698. /**文件上传中处理 */
  699. const handleFileUploadProgress = () => {
  700. upload.isUploading = true;
  701. };
  702. /** 文件上传成功处理 */
  703. const handleFileSuccess = (response: any, file: UploadFile) => {
  704. upload.open = false;
  705. upload.isUploading = false;
  706. uploadRef.value?.handleRemove(file);
  707. ElMessageBox.alert(
  708. "<div style='overflow: auto;overflow-x: hidden;max-height: 70vh;padding: 10px 20px 0;'>" + response.msg + '</div>',
  709. t('user.dialog.importResult'),
  710. {
  711. dangerouslyUseHTMLString: true
  712. }
  713. );
  714. getList();
  715. };
  716. /** 提交上传文件 */
  717. function submitFileForm() {
  718. uploadRef.value?.submit();
  719. }
  720. /** 重置操作表单 */
  721. const reset = () => {
  722. form.value = { ...initFormData };
  723. selectedProjectIds.value = [];
  724. isAllProjectsSelected.value = false;
  725. userFormRef.value?.resetFields();
  726. };
  727. /** 取消按钮 */
  728. const cancel = () => {
  729. dialog.visible = false;
  730. reset();
  731. };
  732. /** 新增按钮操作 */
  733. const handleAdd = async () => {
  734. reset();
  735. const { data } = await api.getUser();
  736. await getProjectList();
  737. dialog.visible = true;
  738. dialog.title = t('user.dialog.add');
  739. postOptions.value = data.posts;
  740. roleOptions.value = data.roles;
  741. form.value.password = initPassword.value.toString();
  742. };
  743. /** 修改按钮操作 */
  744. const handleUpdate = async (row?: UserForm) => {
  745. reset();
  746. const userId = row?.userId || ids.value[0];
  747. const { data } = await api.getUser(userId);
  748. await getProjectList();
  749. dialog.visible = true;
  750. dialog.title = t('user.dialog.edit');
  751. Object.assign(form.value, data.user);
  752. postOptions.value = data.posts;
  753. roleOptions.value = Array.from(new Map([...data.roles, ...data.user.roles].map((role) => [role.roleId, role])).values());
  754. form.value.postIds = data.postIds;
  755. form.value.roleIds = data.roleIds;
  756. form.value.password = '';
  757. // 初始化项目选择
  758. if (data.user.projects === '*') {
  759. // 全选状态
  760. isAllProjectsSelected.value = true;
  761. selectedProjectIds.value = [];
  762. } else if (data.user.projects) {
  763. // 手动选择状态
  764. isAllProjectsSelected.value = false;
  765. selectedProjectIds.value = data.user.projects
  766. .split(',')
  767. .map((id: string) => parseInt(id))
  768. .filter((id: number) => !isNaN(id));
  769. } else {
  770. // 未选择,确保 projects 为空字符串而不是 null 或 undefined
  771. isAllProjectsSelected.value = false;
  772. selectedProjectIds.value = [];
  773. form.value.projects = '';
  774. }
  775. };
  776. /** 提交按钮 */
  777. const submitForm = () => {
  778. userFormRef.value?.validate(async (valid: boolean) => {
  779. if (valid) {
  780. // 确保 projects 字段为空字符串而不是 null 或 undefined
  781. if (!form.value.projects) {
  782. form.value.projects = '';
  783. }
  784. if (form.value.userId) {
  785. // 自己编辑自己的情况下 不允许编辑角色部门岗位
  786. if (form.value.userId == useUserStore().userId) {
  787. form.value.roleIds = null;
  788. form.value.deptId = null;
  789. form.value.postIds = null;
  790. }
  791. await api.updateUser(form.value);
  792. } else {
  793. await api.addUser(form.value);
  794. }
  795. proxy?.$modal.msgSuccess(t('user.message.operationSuccess'));
  796. dialog.visible = false;
  797. await getList();
  798. }
  799. });
  800. };
  801. /**
  802. * 关闭用户弹窗
  803. */
  804. const closeDialog = () => {
  805. dialog.visible = false;
  806. resetForm();
  807. };
  808. /**
  809. * 重置表单
  810. */
  811. const resetForm = () => {
  812. userFormRef.value?.resetFields();
  813. userFormRef.value?.clearValidate();
  814. form.value.id = undefined;
  815. form.value.status = '1';
  816. form.value.projects = '';
  817. selectedProjectIds.value = [];
  818. isAllProjectsSelected.value = false;
  819. };
  820. onMounted(() => {
  821. getDeptTree(); // 初始化部门数据
  822. getList(); // 初始化列表数据
  823. proxy?.getConfigKey('sys.user.initPassword').then((response) => {
  824. initPassword.value = response.data;
  825. });
  826. });
  827. async function handleDeptChange(value: number | string) {
  828. const response = await optionselect(value);
  829. postOptions.value = response.data;
  830. form.value.postIds = [];
  831. }
  832. </script>