userCrud.ts 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  1. import request from "@/utils/request";
  2. import { PageOption } from "@smallwei/avue";
  3. import { ElMessageBox, ElMessage } from "element-plus";
  4. import { useUserStoreHook } from "@/store/modules/user";
  5. import { checkPerm } from "@/directive/permission";
  6. import { configs } from "@typescript-eslint/eslint-plugin";
  7. interface UseCrudConfig {
  8. // 模块的url,用来进行增删改查
  9. src?: string;
  10. dataListUrl?: string;
  11. afterDataList?: Function;
  12. // 需要操作的数据
  13. row?: any;
  14. // done用于结束操作
  15. done?: () => void;
  16. // 具体操作的行
  17. index?: number;
  18. // 用于中断操作
  19. loading?: () => void;
  20. // 额外查询参数 一般用search的值就可以了
  21. params?: object;
  22. // 是否是编辑,如果是编辑调用更新,否则调用新增
  23. isEdit?: boolean;
  24. // 用于多选选中后添加进入,toDeleteIds中的key值。
  25. multipleSelectKey?: string;
  26. }
  27. export const useCrud = (config?: UseCrudConfig) => {
  28. const url = ref(config?.src);
  29. const commonConfig = ref(config);
  30. /** 表格配置属性 */
  31. const option = ref({
  32. searchIcon: true,
  33. // searchSpan: 4,
  34. searchIndex: 3, //searchIcon是否启用功能按钮, searchIndex配置收缩展示的个数,默认为2个
  35. index: true, //是否显示第几项
  36. indexLabel: "序号",
  37. indexWidth: "55px",
  38. refreshBtn: false,
  39. border: true,
  40. viewBtn: true,
  41. tip: false, //选中的提示
  42. });
  43. const data = ref<any>([]); //表格数据
  44. const form = ref({}); //新增或者编辑弹出的表单绑定值
  45. /** 表格顶部搜索的表单的变量 v-model */
  46. const search = ref({});
  47. /** 表格的分页数据 v-model */
  48. const page = ref<PageOption>({
  49. total: 0,
  50. currentPage: 1,
  51. pageSize: 10,
  52. });
  53. /** 配置项结构 v-model */
  54. // defaults ?: AvueCrudDefaults;
  55. // 多选返回的数组,命名是因为后来增加了很多功能,现在这个数组里面的值是根据multipleSelectKey来决定
  56. const toDeleteIds = ref<Array<string>>([]);
  57. // 多选返回的数组,返回每一行的row数据
  58. const selectedRows = ref<any[]>([]);
  59. const save = async (config?: UseCrudConfig) => {
  60. try {
  61. const path = config?.isEdit ? "/update" : "/add";
  62. const res = (await request({
  63. url: `${url.value}${path}`,
  64. method: "post",
  65. data: form.value,
  66. })) as any;
  67. if (res?.code == 200) {
  68. Methords.dataList();
  69. config?.done && config?.done();
  70. ElMessage.success(res?.msg ?? "");
  71. } else {
  72. config?.loading && config?.loading();
  73. ElMessage.error(res?.msg ?? "");
  74. }
  75. } catch (err) {
  76. config?.loading && config?.loading();
  77. }
  78. };
  79. const handleSearchData = () => {
  80. // const array = [null, undefined, ""];
  81. search.value = Object.fromEntries(
  82. Object.entries(search.value).filter(
  83. ([key, value]) =>
  84. value !== null &&
  85. value !== undefined &&
  86. Object.keys(value).length !== 0
  87. )
  88. );
  89. };
  90. /**
  91. * 表格增删改查等基本方法
  92. */
  93. const Methords = {
  94. /**
  95. * 查询方法
  96. */
  97. dataList: async (config?: UseCrudConfig) => {
  98. handleSearchData();
  99. try {
  100. const res = await request({
  101. url: commonConfig.value?.dataListUrl ?? `${url.value}/page`,
  102. method: "post",
  103. data: {
  104. pageNo: page.value.currentPage,
  105. pageSize: page.value.pageSize,
  106. ...(commonConfig.value?.params ?? {}),
  107. ...search.value,
  108. },
  109. });
  110. if (res?.data) {
  111. if (res?.data instanceof Array) {
  112. data.value = res?.data || [];
  113. page.value.total = res?.data?.length || 0;
  114. } else {
  115. data.value = res?.data?.records || [];
  116. page.value.total = res?.data?.totalCount || 0;
  117. }
  118. if (commonConfig.value?.afterDataList) {
  119. commonConfig.value?.afterDataList();
  120. }
  121. }
  122. config?.done && config?.done();
  123. } catch (err) {
  124. config?.loading && config?.loading();
  125. } finally {
  126. config?.done && config?.done();
  127. }
  128. },
  129. dataEditList: async (config?: UseCrudConfig) => {
  130. handleSearchData();
  131. try {
  132. const res = await request({
  133. url: `${url.value}/page`,
  134. method: "post",
  135. data: {
  136. pageNo: page.value.currentPage,
  137. pageSize: page.value.pageSize,
  138. ...search.value,
  139. ...(commonConfig.value?.params ?? {}),
  140. },
  141. });
  142. if (res?.data) {
  143. if (res?.data instanceof Array) {
  144. data.value = res?.data || [];
  145. page.value.total = res?.data?.length || 0;
  146. } else {
  147. data.value = res?.data?.records || [];
  148. for (let i = 0; i < data.value.length; i++) {
  149. data.value[i].$cellEdit = true;
  150. if (
  151. data.value[i].children != undefined &&
  152. data.value[i].children != null &&
  153. data.value[i].children.length > 0
  154. ) {
  155. for (let j = 0; j < data.value[i].children.length; j++) {
  156. data.value[i].children[j].$cellEdit = true;
  157. }
  158. }
  159. }
  160. page.value.total = res?.data?.totalCount || 0;
  161. }
  162. }
  163. config?.done && config?.done();
  164. } catch (err) {
  165. config?.loading && config?.loading();
  166. } finally {
  167. config?.done && config?.done();
  168. }
  169. },
  170. dataNoPageList: async (config?: UseCrudConfig) => {
  171. handleSearchData();
  172. try {
  173. const res = await request({
  174. url: `${url.value}/list`,
  175. method: "post",
  176. data: {
  177. ...search.value,
  178. },
  179. });
  180. if (res?.data) {
  181. data.value = res?.data || [];
  182. }
  183. config?.done && config?.done();
  184. } catch (err) {
  185. config?.loading && config?.loading();
  186. } finally {
  187. config?.done && config?.done();
  188. }
  189. },
  190. createRow: (row: any, done: () => void, loading: () => void) => {
  191. save({ row: row, done: done, loading: loading });
  192. },
  193. updateRow: (
  194. row: any,
  195. index: number,
  196. done: () => void,
  197. loading: () => void
  198. ) => {
  199. save({
  200. row: row,
  201. done: done,
  202. loading: loading,
  203. index: index,
  204. isEdit: true,
  205. });
  206. },
  207. deleteRow: async (row: any, index: number, done: () => void) => {
  208. ElMessageBox.confirm("是否删除所选中数据?", "提示", {
  209. confirmButtonText: "确定",
  210. cancelButtonText: "取消",
  211. type: "warning",
  212. }).then(async () => {
  213. if (row.children && row.children.length > 0) {
  214. ElMessage.error("请先解绑下级关系");
  215. return;
  216. }
  217. try {
  218. const res = await request({
  219. url: `${url.value}/del`,
  220. method: "post",
  221. data: row,
  222. });
  223. Methords.dataList();
  224. config?.done && config?.done();
  225. } catch (err) {
  226. config?.loading && config?.loading();
  227. } finally {
  228. config?.done && config?.done();
  229. }
  230. });
  231. },
  232. // 设置selection: true,后监听选中改变事件,将Id存入数组
  233. selectionChange: (rows?: any[]) => {
  234. toDeleteIds.value = [];
  235. selectedRows.value = [];
  236. rows?.forEach((element) => {
  237. toDeleteIds.value.push(
  238. element[commonConfig.value?.multipleSelectKey ?? "id"]
  239. );
  240. selectedRows.value.push(element);
  241. });
  242. },
  243. /**
  244. * 表格上方的多选删除方法
  245. */
  246. multipleDelete: async () => {
  247. ElMessageBox.confirm("是否删除所选中数据?", "提示", {
  248. confirmButtonText: "确定",
  249. cancelButtonText: "取消",
  250. type: "warning",
  251. }).then(async () => {
  252. try {
  253. const res = await request({
  254. url: `${url.value}/batch-del`,
  255. method: "post",
  256. data: { ids: toDeleteIds.value },
  257. });
  258. Methords.dataList();
  259. config?.done && config?.done();
  260. } catch (err) {
  261. config?.loading && config?.loading();
  262. } finally {
  263. config?.done && config?.done();
  264. }
  265. });
  266. },
  267. /**
  268. * 表格拖拽后批量保存
  269. * */
  270. multipleUpdate: async () => {
  271. try {
  272. // 由于数据带有$开头的属性,所以需要处理下,改为只传id和sortNum。
  273. const dtosArray: { id: string; sortNum: number }[] = [];
  274. for (let i = 0; i < data.value.length; i++) {
  275. let cur = page.value.currentPage ?? 1;
  276. cur = cur - 1;
  277. const size = page.value.pageSize ?? 10;
  278. let sortNum = cur * size;
  279. sortNum = sortNum + i;
  280. dtosArray.push({ id: data.value[i].id, sortNum: sortNum });
  281. }
  282. const res = await request({
  283. url: `${url.value}/batch-update`,
  284. method: "post",
  285. data: dtosArray,
  286. });
  287. Methords.dataList();
  288. config?.done && config?.done();
  289. } catch (err) {
  290. config?.loading && config?.loading();
  291. } finally {
  292. config?.done && config?.done();
  293. }
  294. },
  295. /**
  296. * 点击搜索按钮触发
  297. */
  298. searchChange: async (params: any, done: () => void) => {
  299. // 点击搜索的时候重置search的值,要不然会传过去所有的值包括空字符串的值
  300. search.value = params;
  301. page.value.currentPage = 1;
  302. Methords.dataList({ done: done });
  303. },
  304. /**
  305. * 点击清空按钮触发
  306. */
  307. resetChange: async (item: any) => {
  308. page.value.currentPage = 1;
  309. Methords.dataList();
  310. },
  311. };
  312. /**
  313. * 表格辅助方法,按钮权限,导入导出
  314. */
  315. const Utils = {
  316. checkBtnPerm: (str: string) => {
  317. // 「超级管理员」拥有所有的按钮权限
  318. const { roles, perms } = useUserStoreHook().user;
  319. if (roles.includes("ROOT")) {
  320. return true;
  321. }
  322. const hasPerm = perms?.some((perm) => {
  323. return str.includes(perm);
  324. });
  325. return hasPerm;
  326. },
  327. /**
  328. * 根据返回的数据下载文件
  329. */
  330. downloadFile: (response: any) => {
  331. const fileData = response.data;
  332. const fileName = decodeURI(
  333. response.headers["content-disposition"].split(";")[1].split("=")[1]
  334. );
  335. const fileType =
  336. "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet;charset=utf-8";
  337. const blob = new Blob([fileData], { type: fileType });
  338. const downloadUrl = window.URL.createObjectURL(blob);
  339. const downloadLink = document.createElement("a");
  340. downloadLink.href = downloadUrl;
  341. downloadLink.download = fileName;
  342. document.body.appendChild(downloadLink);
  343. downloadLink.click();
  344. document.body.removeChild(downloadLink);
  345. window.URL.revokeObjectURL(downloadUrl);
  346. },
  347. /**
  348. * 下载模版 传入url
  349. */
  350. downloadTemplate: async (urlStr: string) => {
  351. const response = await request({
  352. url: urlStr,
  353. method: "get",
  354. responseType: "arraybuffer",
  355. });
  356. Utils.downloadFile(response);
  357. },
  358. /**
  359. * 根据搜索项导出数据
  360. */
  361. exportData: async (urlStr: string) => {
  362. handleSearchData();
  363. const response = await request({
  364. url: urlStr,
  365. method: "post",
  366. data: search.value,
  367. responseType: "arraybuffer",
  368. });
  369. Utils.downloadFile(response);
  370. },
  371. };
  372. return {
  373. url,
  374. option,
  375. data,
  376. form,
  377. search,
  378. page,
  379. toDeleteIds,
  380. Methords,
  381. Utils,
  382. selectedRows,
  383. commonConfig,
  384. };
  385. };