category.vue 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570
  1. <template>
  2. <div>
  3. <basic-container>
  4. <avue-crud
  5. :option="option"
  6. :table-loading="loading"
  7. :data="data"
  8. ref="crud"
  9. v-model="form"
  10. :permission="permissionList"
  11. :before-open="beforeOpen"
  12. :before-close="beforeClose"
  13. @row-del="rowDel"
  14. @row-update="rowUpdate"
  15. @row-save="rowSave"
  16. @search-change="searchChange"
  17. @search-reset="searchReset"
  18. @selection-change="selectionChange"
  19. @refresh-change="refreshChange"
  20. @on-load="onLoad"
  21. >
  22. <template slot="menuLeft">
  23. <el-button
  24. type="danger"
  25. size="small"
  26. icon="el-icon-delete"
  27. v-if="permission.category_delete"
  28. plain
  29. @click="handleDelete"
  30. >
  31. 删除
  32. </el-button>
  33. </template>
  34. <template slot-scope="{ row }" slot="status">
  35. <el-switch
  36. v-model="row.status"
  37. :active-value="1"
  38. :inactive-value="0"
  39. active-color="#13ce66"
  40. inactive-color="#ff4949"
  41. @change="handleStatusChange(row)"
  42. :disabled="!permission.category_edit"
  43. >
  44. </el-switch>
  45. </template>
  46. <template slot-scope="{ row }" slot="isSystem">
  47. <el-tag :type="row.isSystem ? 'warning' : 'success'">
  48. {{ row.isSystem ? '系统分类' : '自定义分类' }}
  49. </el-tag>
  50. </template>
  51. </avue-crud>
  52. </basic-container>
  53. </div>
  54. </template>
  55. <script>
  56. /**
  57. * 公告分类管理页面
  58. * @description 提供公告分类的完整CRUD功能,包括状态管理
  59. * @author AI Assistant
  60. * @version 1.0.0
  61. */
  62. import {
  63. getCategoryList,
  64. addCategory,
  65. updateCategory,
  66. removeCategory,
  67. getCategoryDetail,
  68. updateCategoryStatus
  69. } from "@/api/announcement/category";
  70. import { mapGetters } from "vuex";
  71. /**
  72. * 分类数据类型定义
  73. * @typedef {Object} CategoryItem
  74. * @property {number} id - 分类ID
  75. * @property {string} name - 分类名称
  76. * @property {number} sortOrder - 排序
  77. * @property {number} orgId - 组织ID
  78. * @property {string} orgCode - 组织编码
  79. * @property {string} orgName - 组织名称
  80. * @property {number} isSystem - 是否系统分类 (0-否, 1-是)
  81. * @property {string} remark - 备注
  82. * @property {number} createUser - 创建用户ID
  83. * @property {number} createDept - 创建部门ID
  84. * @property {string|null} createTime - 创建时间
  85. * @property {number|null} updateUser - 更新用户ID
  86. * @property {string|null} updateTime - 更新时间
  87. * @property {number} status - 状态 (0-禁用, 1-启用)
  88. * @property {number} isDeleted - 是否删除 (0-否, 1-是)
  89. */
  90. /**
  91. * 分类表单数据类型
  92. * @typedef {Object} CategoryForm
  93. * @property {number} [id] - 分类ID(编辑时存在)
  94. * @property {string} name - 分类名称
  95. * @property {number} sortOrder - 排序
  96. * @property {number} orgId - 组织ID
  97. * @property {string} orgCode - 组织编码
  98. * @property {string} orgName - 组织名称
  99. * @property {string} remark - 备注
  100. * @property {number} status - 状态
  101. */
  102. /**
  103. * 查询参数类型
  104. * @typedef {Object} QueryParams
  105. * @property {string} [name] - 分类名称
  106. * @property {string} [orgName] - 组织名称
  107. * @property {number} [status] - 状态
  108. */
  109. export default {
  110. name: "CategoryManagement",
  111. data() {
  112. return {
  113. /** @type {CategoryForm} 表单数据 */
  114. form: {},
  115. /** @type {Array<CategoryItem>} 选中的行数据 */
  116. selectionList: [],
  117. /** @type {QueryParams} 查询条件 */
  118. query: {},
  119. /** @type {boolean} 表格加载状态 */
  120. loading: true,
  121. /** @type {Array<CategoryItem>} 表格数据 */
  122. data: [],
  123. /** @type {Object} 表格配置选项 */
  124. option: {
  125. height: 'auto',
  126. calcHeight: 30,
  127. tip: false,
  128. searchShow: true,
  129. searchMenuSpan: 6,
  130. border: true,
  131. index: true,
  132. selection: true,
  133. viewBtn: true,
  134. dialogClickModal: false,
  135. column: [
  136. {
  137. label: "分类名称",
  138. prop: "name",
  139. search: true,
  140. rules: [
  141. {
  142. required: true,
  143. message: "请输入分类名称",
  144. trigger: "blur"
  145. },
  146. {
  147. min: 2,
  148. max: 50,
  149. message: "分类名称长度在2到50个字符",
  150. trigger: "blur"
  151. }
  152. ]
  153. },
  154. {
  155. label: "组织名称",
  156. prop: "orgName",
  157. search: true,
  158. rules: [
  159. {
  160. required: true,
  161. message: "请输入组织名称",
  162. trigger: "blur"
  163. }
  164. ]
  165. },
  166. {
  167. label: "组织编码",
  168. prop: "orgCode",
  169. rules: [
  170. {
  171. required: true,
  172. message: "请输入组织编码",
  173. trigger: "blur"
  174. },
  175. {
  176. pattern: /^[A-Z0-9_]+$/,
  177. message: "组织编码只能包含大写字母、数字和下划线",
  178. trigger: "blur"
  179. }
  180. ]
  181. },
  182. {
  183. label: "组织ID",
  184. prop: "orgId",
  185. type: "number",
  186. rules: [
  187. {
  188. required: true,
  189. message: "请输入组织ID",
  190. trigger: "blur"
  191. }
  192. ]
  193. },
  194. {
  195. label: "排序",
  196. prop: "sortOrder",
  197. type: "number",
  198. value: 0,
  199. rules: [
  200. {
  201. type: "number",
  202. min: 0,
  203. max: 9999,
  204. message: "排序值范围为0-9999",
  205. trigger: "blur"
  206. }
  207. ]
  208. },
  209. {
  210. label: "状态",
  211. prop: "status",
  212. type: "select",
  213. slot: true,
  214. dicData: [
  215. {
  216. label: "启用",
  217. value: 1
  218. },
  219. {
  220. label: "禁用",
  221. value: 0
  222. }
  223. ],
  224. search: true,
  225. value: 1
  226. },
  227. {
  228. label: "分类类型",
  229. prop: "isSystem",
  230. slot: true,
  231. addDisplay: false,
  232. editDisplay: false
  233. },
  234. {
  235. label: "备注",
  236. prop: "remark",
  237. type: "textarea",
  238. span: 24,
  239. hide: true,
  240. rules: [
  241. {
  242. max: 500,
  243. message: "备注不能超过500个字符",
  244. trigger: "blur"
  245. }
  246. ]
  247. },
  248. {
  249. label: "创建时间",
  250. prop: "createTime",
  251. type: "datetime",
  252. format: "yyyy-MM-dd HH:mm:ss",
  253. valueFormat: "yyyy-MM-dd HH:mm:ss",
  254. addDisplay: false,
  255. editDisplay: false,
  256. width: 180
  257. }
  258. ]
  259. }
  260. };
  261. },
  262. computed: {
  263. ...mapGetters(["permission", "userInfo"]),
  264. /**
  265. * 权限列表配置
  266. * @returns {Object} 权限配置对象
  267. */
  268. permissionList() {
  269. return {
  270. addBtn: this.vaildData(this.permission.category_add, false),
  271. viewBtn: this.vaildData(this.permission.category_view, false),
  272. delBtn: this.vaildData(this.permission.category_delete, false),
  273. editBtn: this.vaildData(this.permission.category_edit, false)
  274. };
  275. },
  276. /**
  277. * 表格行主键
  278. * @returns {string} 主键字段名
  279. */
  280. ids() {
  281. const ids = [];
  282. this.selectionList.forEach(ele => {
  283. ids.push(ele.id);
  284. });
  285. return ids.join(",");
  286. }
  287. },
  288. methods: {
  289. /**
  290. * 删除选中的分类
  291. * @description 批量删除选中的分类
  292. */
  293. async handleDelete() {
  294. if (this.selectionList.length === 0) {
  295. this.$message.warning("请选择至少一条数据");
  296. return;
  297. }
  298. // 检查是否包含系统分类
  299. const hasSystemCategory = this.selectionList.some(item => item.isSystem === 1);
  300. if (hasSystemCategory) {
  301. this.$message.error("系统分类不能删除");
  302. return;
  303. }
  304. try {
  305. await this.$confirm("确定将选择数据删除?", {
  306. confirmButtonText: "确定",
  307. cancelButtonText: "取消",
  308. type: "warning"
  309. });
  310. await removeCategory(this.ids);
  311. this.onLoad();
  312. this.$message({
  313. type: "success",
  314. message: "操作成功!"
  315. });
  316. this.$refs.crud.toggleSelection();
  317. } catch (error) {
  318. this.$message({
  319. type: "info",
  320. message: "已取消删除"
  321. });
  322. }
  323. },
  324. /**
  325. * 状态变更处理
  326. * @param {CategoryItem} row - 行数据
  327. */
  328. async handleStatusChange(row) {
  329. const statusText = row.status === 1 ? '启用' : '禁用';
  330. try {
  331. await this.$confirm(`确定${statusText}该分类吗?`, {
  332. confirmButtonText: "确定",
  333. cancelButtonText: "取消",
  334. type: "warning"
  335. });
  336. await updateCategoryStatus(row.id, row.status);
  337. this.$message({
  338. type: "success",
  339. message: `${statusText}成功!`
  340. });
  341. } catch (error) {
  342. // 恢复原状态
  343. row.status = row.status === 1 ? 0 : 1;
  344. this.$message({
  345. type: "info",
  346. message: "已取消操作"
  347. });
  348. }
  349. },
  350. /**
  351. * 表单打开前的回调
  352. * @param {Function} done - 完成回调
  353. * @param {string} type - 操作类型 (add/edit/view)
  354. */
  355. async beforeOpen(done, type) {
  356. if (["edit", "view"].includes(type)) {
  357. try {
  358. const res = await getCategoryDetail(this.form.id);
  359. this.form = res.data.data;
  360. } catch (error) {
  361. console.error('获取分类详情失败:', error);
  362. }
  363. } else if (type === "add") {
  364. // 新增时设置默认值
  365. this.form = {
  366. createDept: this.userInfo.deptId || 1,
  367. createUser: this.userInfo.userId || 1,
  368. orgId: 1,
  369. orgCode: "ORG_0001",
  370. orgName: "库比森",
  371. sortOrder: 0,
  372. status: 1,
  373. remark: ""
  374. };
  375. }
  376. done();
  377. },
  378. /**
  379. * 表单关闭前的回调
  380. * @param {Function} done - 完成回调
  381. */
  382. beforeClose(done) {
  383. this.form = {};
  384. done();
  385. },
  386. /**
  387. * 行删除回调
  388. * @param {CategoryItem} row - 行数据
  389. * @param {number} index - 行索引
  390. */
  391. async rowDel(row, index) {
  392. if (row.isSystem === 1) {
  393. this.$message.error("系统分类不能删除");
  394. return;
  395. }
  396. try {
  397. await this.$confirm("确定将选择数据删除?", {
  398. confirmButtonText: "确定",
  399. cancelButtonText: "取消",
  400. type: "warning"
  401. });
  402. await removeCategory(row.id);
  403. this.onLoad();
  404. this.$message({
  405. type: "success",
  406. message: "操作成功!"
  407. });
  408. } catch (error) {
  409. console.error('删除分类失败:', error);
  410. }
  411. },
  412. /**
  413. * 行更新回调
  414. * @param {CategoryForm} row - 行数据
  415. * @param {number} index - 行索引
  416. * @param {Function} done - 完成回调
  417. * @param {Function} loading - 加载状态回调
  418. */
  419. async rowUpdate(row, index, done, loading) {
  420. try {
  421. await updateCategory(row);
  422. this.onLoad();
  423. this.$message({
  424. type: "success",
  425. message: "操作成功!"
  426. });
  427. done();
  428. } catch (error) {
  429. console.error('更新分类失败:', error);
  430. loading();
  431. }
  432. },
  433. /**
  434. * 行保存回调
  435. * @param {CategoryForm} row - 行数据
  436. * @param {Function} done - 完成回调
  437. * @param {Function} loading - 加载状态回调
  438. */
  439. async rowSave(row, done, loading) {
  440. try {
  441. await addCategory(row);
  442. this.onLoad();
  443. this.$message({
  444. type: "success",
  445. message: "操作成功!"
  446. });
  447. done();
  448. } catch (error) {
  449. console.error('新增分类失败:', error);
  450. loading();
  451. }
  452. },
  453. /**
  454. * 搜索条件变化回调
  455. * @param {QueryParams} params - 搜索参数
  456. * @param {Function} done - 完成回调
  457. */
  458. searchChange(params, done) {
  459. this.query = params;
  460. this.onLoad(params);
  461. done();
  462. },
  463. /**
  464. * 搜索重置回调
  465. */
  466. searchReset() {
  467. this.query = {};
  468. this.onLoad();
  469. },
  470. /**
  471. * 选择变化回调
  472. * @param {Array<CategoryItem>} list - 选中的行数据列表
  473. */
  474. selectionChange(list) {
  475. this.selectionList = list;
  476. },
  477. /**
  478. * 清空选择
  479. */
  480. selectionClear() {
  481. this.selectionList = [];
  482. this.$refs.crud.toggleSelection();
  483. },
  484. /**
  485. * 刷新回调
  486. */
  487. refreshChange() {
  488. this.onLoad(this.query);
  489. },
  490. /**
  491. * 加载数据
  492. * @param {QueryParams} params - 查询参数
  493. */
  494. async onLoad(params = {}) {
  495. this.loading = true;
  496. try {
  497. const res = await getCategoryList(Object.assign(params, this.query));
  498. const data = res.data.data;
  499. this.data = Array.isArray(data) ? data : [];
  500. this.selectionClear();
  501. } catch (error) {
  502. console.error('加载分类列表失败:', error);
  503. this.data = [];
  504. } finally {
  505. this.loading = false;
  506. }
  507. }
  508. },
  509. /**
  510. * 组件挂载后初始化数据
  511. */
  512. mounted() {
  513. this.onLoad();
  514. }
  515. };
  516. </script>
  517. <style scoped>
  518. /* 组件样式 */
  519. .el-tag {
  520. margin-right: 8px;
  521. }
  522. .el-switch {
  523. margin: 0;
  524. }
  525. </style>