index.vue 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654
  1. <template>
  2. <basic-container>
  3. <avue-crud
  4. :option="option"
  5. :data="data"
  6. ref="crud"
  7. v-model="form"
  8. :page.sync="page"
  9. :permission="permissionList"
  10. :before-open="beforeOpen"
  11. :table-loading="loading"
  12. @row-update="rowUpdate"
  13. @row-save="rowSave"
  14. @search-change="searchChange"
  15. @search-reset="searchReset"
  16. @selection-change="selectionChange"
  17. @current-change="currentChange"
  18. @size-change="sizeChange"
  19. @refresh-change="refreshChange"
  20. @on-load="onLoad"
  21. >
  22. <template slot="menuLeft">
  23. <el-button
  24. type="primary"
  25. size="small"
  26. icon="el-icon-view"
  27. plain
  28. @click="handleDetail"
  29. v-if="selectionList.length === 1"
  30. >
  31. 查看详情
  32. </el-button>
  33. </template>
  34. <template slot-scope="{row}" slot="categoryName">
  35. <el-tag type="primary">{{ row.categoryName }}</el-tag>
  36. </template>
  37. <template slot-scope="{row}" slot="status">
  38. <el-tag :type="row.status === 1 ? 'success' : 'danger'">
  39. {{ row.status === 1 ? '正常' : '禁用' }}
  40. </el-tag>
  41. </template>
  42. <template slot-scope="{row}" slot="visibleRoles">
  43. <el-tag>{{ getVisibleRolesText(row.visibleRoles) }}</el-tag>
  44. </template>
  45. </avue-crud>
  46. <!-- 详情对话框 -->
  47. <el-dialog title="公告详情" :visible.sync="detailVisible" width="60%" :close-on-click-modal="false">
  48. <div class="detail-content" v-if="currentDetail.id">
  49. <div class="detail-info">
  50. <p><strong>公告标题:</strong>{{ currentDetail.title }}</p>
  51. <p><strong>分类:</strong>{{ currentDetail.categoryName }}</p>
  52. <p><strong>组织:</strong>{{ currentDetail.orgName }}</p>
  53. <p><strong>创建时间:</strong>{{ currentDetail.createTime }}</p>
  54. <p><strong>可见角色:</strong>{{ getVisibleRolesText(currentDetail.visibleRoles) }}</p>
  55. <p><strong>状态:</strong>
  56. <el-tag :type="currentDetail.status === 1 ? 'success' : 'danger'">
  57. {{ currentDetail.status === 1 ? '正常' : '禁用' }}
  58. </el-tag>
  59. </p>
  60. </div>
  61. <div class="detail-body" v-html="currentDetail.content"></div>
  62. </div>
  63. <span slot="footer" class="dialog-footer">
  64. <el-button @click="detailVisible = false">关 闭</el-button>
  65. </span>
  66. </el-dialog>
  67. </basic-container>
  68. </template>
  69. <script>
  70. import { getList, update, add, getAnnouncement, getCategoryList } from "@/api/announcement";
  71. import { mapGetters } from "vuex";
  72. /**
  73. * 公告数据类型定义
  74. * @typedef {Object} NoticeItem
  75. * @property {string} id - 公告ID
  76. * @property {string} title - 公告标题
  77. * @property {string} content - 公告内容
  78. * @property {string} categoryId - 分类ID
  79. * @property {string} categoryName - 分类名称
  80. * @property {number} orgId - 组织ID
  81. * @property {string} orgCode - 组织编码
  82. * @property {string} orgName - 组织名称
  83. * @property {string} visibleRoles - 可见角色
  84. * @property {Object|null} brandScope - 品牌范围
  85. * @property {Object|null} customerBlacklist - 客户黑名单
  86. * @property {string} remark - 备注
  87. * @property {string} createTime - 创建时间
  88. * @property {string} updateTime - 更新时间
  89. * @property {number} status - 状态
  90. * @property {number} isDeleted - 是否删除
  91. */
  92. /**
  93. * 分类选项类型定义
  94. * @typedef {Object} CategoryOption
  95. * @property {string} id - 分类ID
  96. * @property {string} name - 分类名称
  97. * @property {string} value - 选项值
  98. * @property {string} label - 选项标签
  99. */
  100. /**
  101. * 分页信息类型定义
  102. * @typedef {Object} PageInfo
  103. * @property {number} pageSize - 每页大小
  104. * @property {number} currentPage - 当前页码
  105. * @property {number} total - 总记录数
  106. */
  107. /**
  108. * 查询参数类型定义
  109. * @typedef {Object} QueryParams
  110. * @property {string} [title] - 公告标题
  111. * @property {string} [categoryId] - 分类ID
  112. * @property {string} [content] - 公告内容
  113. */
  114. /**
  115. * 公告管理组件
  116. * @component NoticeIndex
  117. */
  118. export default {
  119. name: 'NoticeIndex',
  120. data() {
  121. return {
  122. /** @type {NoticeItem} 表单数据 */
  123. form: {},
  124. /** @type {QueryParams} 查询参数 */
  125. query: {},
  126. /** @type {boolean} 加载状态 */
  127. loading: true,
  128. /** @type {boolean} 详情对话框显示状态 */
  129. detailVisible: false,
  130. /** @type {NoticeItem} 当前查看的详情数据 */
  131. currentDetail: {},
  132. /** @type {PageInfo} 分页信息 */
  133. page: {
  134. pageSize: 10,
  135. currentPage: 1,
  136. total: 0
  137. },
  138. /** @type {NoticeItem[]} 选中的数据列表 */
  139. selectionList: [],
  140. /** @type {CategoryOption[]} 分类选项列表 */
  141. categoryOptions: [],
  142. /** @type {Object} 表格配置选项 */
  143. option: {
  144. height: 'auto',
  145. calcHeight: 30,
  146. dialogWidth: 1000,
  147. labelWidth: 120,
  148. tip: false,
  149. searchShow: true,
  150. searchMenuSpan: 6,
  151. border: true,
  152. index: true,
  153. viewBtn: true,
  154. selection: true,
  155. excelBtn: false,
  156. columnBtn: false,
  157. delBtn: false, // 根据需求移除删除功能
  158. dialogClickModal: false,
  159. column: [
  160. {
  161. label: "公告标题",
  162. prop: "title",
  163. span: 12,
  164. search: true,
  165. overHidden: true,
  166. rules: [{
  167. required: true,
  168. message: "请输入公告标题",
  169. trigger: "blur"
  170. }]
  171. },
  172. {
  173. label: "分类",
  174. prop: "categoryId",
  175. type: "select",
  176. dicData: [],
  177. props: {
  178. label: "name",
  179. value: "id"
  180. },
  181. slot: true,
  182. search: true,
  183. span: 12,
  184. rules: [{
  185. required: true,
  186. message: "请选择分类",
  187. trigger: "change"
  188. }]
  189. },
  190. {
  191. label: "组织名称",
  192. prop: "orgName",
  193. span: 12,
  194. overHidden: true,
  195. rules: [{
  196. required: true,
  197. message: "请输入组织名称",
  198. trigger: "blur"
  199. }]
  200. },
  201. {
  202. label: "组织ID",
  203. prop: "orgId",
  204. span: 12,
  205. type: "number",
  206. rules: [{
  207. required: true,
  208. message: "请输入组织名称",
  209. trigger: "blur"
  210. }]
  211. },
  212. {
  213. label: "组织编码",
  214. prop: "orgCode",
  215. span: 12,
  216. rules: [{
  217. required: true,
  218. message: "请输入组织编码",
  219. trigger: "blur"
  220. }]
  221. },
  222. {
  223. label: "可见角色",
  224. prop: "visibleRoles",
  225. type: "select",
  226. dicData: [
  227. { label: "管理员", value: "1" },
  228. { label: "普通用户", value: "2" },
  229. { label: "访客", value: "3" },
  230. { label: "VIP用户", value: "4" }
  231. ],
  232. slot: true,
  233. span: 12,
  234. rules: [{
  235. required: true,
  236. message: "请选择可见角色",
  237. trigger: "change"
  238. }]
  239. },
  240. {
  241. label: "状态",
  242. prop: "status",
  243. type: "select",
  244. dicData: [
  245. { label: "正常", value: 1 },
  246. { label: "禁用", value: 0 }
  247. ],
  248. slot: true,
  249. addDisplay: false,
  250. editDisplay: false,
  251. width: 80
  252. },
  253. {
  254. label: "创建时间",
  255. prop: "createTime",
  256. type: "datetime",
  257. format: "yyyy-MM-dd HH:mm:ss",
  258. valueFormat: "yyyy-MM-dd HH:mm:ss",
  259. addDisplay: false,
  260. editDisplay: false,
  261. overHidden: true,
  262. width: 150
  263. },
  264. {
  265. label: "分类名称",
  266. prop: "categoryName",
  267. hide: true,
  268. addDisplay: false,
  269. editDisplay: false
  270. },
  271. {
  272. label: "备注",
  273. prop: "remark",
  274. type: "textarea",
  275. span: 24,
  276. minRows: 3,
  277. hide: true
  278. },
  279. {
  280. label: "公告内容",
  281. prop: "content",
  282. component: 'AvueUeditor',
  283. options: {
  284. action: '/api/blade-resource/oss/endpoint/put-file',
  285. props: {
  286. res: "data",
  287. url: "link",
  288. }
  289. },
  290. showColumn: false,
  291. hide: true,
  292. minRows: 6,
  293. span: 24,
  294. rules: [{
  295. required: true,
  296. message: "请输入公告内容",
  297. trigger: "blur"
  298. }]
  299. },
  300. {
  301. label: "品牌范围",
  302. prop: "brandScope",
  303. type: "json",
  304. hide: true,
  305. span: 24
  306. },
  307. {
  308. label: "客户黑名单",
  309. prop: "customerBlacklist",
  310. type: "json",
  311. hide: true,
  312. span: 24
  313. }
  314. ]
  315. },
  316. /** @type {NoticeItem[]} 表格数据 */
  317. data: []
  318. };
  319. },
  320. computed: {
  321. ...mapGetters(["permission", "userInfo"]),
  322. /**
  323. * 权限列表
  324. * @returns {Object} 权限配置对象
  325. */
  326. permissionList() {
  327. return {
  328. // addBtn: this.vaildData(this.permission.announcement_add, false),
  329. // viewBtn: this.vaildData(this.permission.announcement_view, false),
  330. // delBtn: false,
  331. // editBtn: this.vaildData(this.permission.announcement_edit, false)
  332. addBtn: true,
  333. viewBtn: true,
  334. delBtn: false,
  335. editBtn: true,
  336. };
  337. },
  338. /**
  339. * 选中的ID字符串
  340. * @returns {string} 逗号分隔的ID字符串
  341. */
  342. ids() {
  343. const ids = [];
  344. this.selectionList.forEach(ele => {
  345. ids.push(ele.id);
  346. });
  347. return ids.join(",");
  348. }
  349. },
  350. created() {
  351. this.loadCategoryOptions();
  352. },
  353. methods: {
  354. /**
  355. * 加载分类选项
  356. * @async
  357. * @returns {Promise<void>}
  358. */
  359. async loadCategoryOptions() {
  360. try {
  361. const res = await getCategoryList();
  362. const categoryData = res.data.data || [];
  363. this.categoryOptions = categoryData
  364. .filter(item => item.status === 1 && item.isDeleted === 0)
  365. .map(item => ({
  366. id: item.id,
  367. name: item.name,
  368. value: item.id,
  369. label: item.name,
  370. orgId: item.orgId,
  371. orgName: item.orgName,
  372. sortOrder: item.sortOrder || 0
  373. }))
  374. .sort((a, b) => a.sortOrder - b.sortOrder);
  375. const categoryColumn = this.option.column.find(col => col.prop === 'categoryId');
  376. if (categoryColumn) {
  377. categoryColumn.dicData = this.categoryOptions;
  378. }
  379. } catch (error) {
  380. console.error('加载分类选项失败:', error);
  381. // 使用默认分类
  382. this.categoryOptions = [
  383. { id: '1', name: '系统公告', value: '1', label: '系统公告' },
  384. { id: '2', name: '部门公告', value: '2', label: '部门公告' }
  385. ];
  386. const categoryColumn = this.option.column.find(col => col.prop === 'categoryId');
  387. if (categoryColumn) {
  388. categoryColumn.dicData = this.categoryOptions;
  389. }
  390. }
  391. },
  392. /**
  393. * 获取可见角色文本
  394. * @param {string} visibleRoles - 可见角色值
  395. * @returns {string} 角色文本
  396. */
  397. getVisibleRolesText(visibleRoles) {
  398. const roleMap = {
  399. '1': '管理员',
  400. '2': '普通用户',
  401. '3': '访客',
  402. '4': 'VIP用户'
  403. };
  404. return roleMap[visibleRoles] || '未知角色';
  405. },
  406. /**
  407. * 查看详情
  408. * @async
  409. * @returns {Promise<void>}
  410. */
  411. async handleDetail() {
  412. if (this.selectionList.length !== 1) {
  413. this.$message.warning("请选择一条数据查看详情");
  414. return;
  415. }
  416. try {
  417. const res = await getAnnouncement(this.selectionList[0].id);
  418. this.currentDetail = res.data.data;
  419. this.detailVisible = true;
  420. } catch (error) {
  421. console.error('获取详情失败:', error);
  422. this.$message.error('获取详情失败');
  423. }
  424. },
  425. /**
  426. * 保存行数据
  427. * @async
  428. * @param {NoticeItem} row - 行数据
  429. * @param {Function} done - 完成回调
  430. * @param {Function} loading - 加载回调
  431. * @returns {Promise<void>}
  432. */
  433. async rowSave(row, done, loading) {
  434. try {
  435. // 设置默认值
  436. const formData = {
  437. ...row,
  438. orgId: this.userInfo.orgId || 1,
  439. orgCode: this.userInfo.orgCode || 'ORG_0001',
  440. orgName: this.userInfo.orgName || '默认组织',
  441. brandScope: row.brandScope || {},
  442. customerBlacklist: row.customerBlacklist || {},
  443. remark: row.remark || ''
  444. };
  445. // 设置分类名称
  446. const selectedCategory = this.categoryOptions.find(cat => cat.id === row.categoryId);
  447. if (selectedCategory) {
  448. formData.categoryName = selectedCategory.name;
  449. }
  450. await add(formData);
  451. this.onLoad(this.page);
  452. this.$message({
  453. type: "success",
  454. message: "操作成功!"
  455. });
  456. done();
  457. } catch (error) {
  458. console.error('保存失败:', error);
  459. this.$message.error('保存失败');
  460. loading();
  461. }
  462. },
  463. /**
  464. * 更新行数据
  465. * @async
  466. * @param {NoticeItem} row - 行数据
  467. * @param {number} index - 行索引
  468. * @param {Function} done - 完成回调
  469. * @param {Function} loading - 加载回调
  470. * @returns {Promise<void>}
  471. */
  472. async rowUpdate(row, index, done, loading) {
  473. try {
  474. // 设置分类名称
  475. const selectedCategory = this.categoryOptions.find(cat => cat.id === row.categoryId);
  476. if (selectedCategory) {
  477. row.categoryName = selectedCategory.name;
  478. }
  479. // 确保必要字段存在
  480. const formData = {
  481. ...row,
  482. brandScope: row.brandScope || {},
  483. customerBlacklist: row.customerBlacklist || {},
  484. remark: row.remark || ''
  485. };
  486. await update(formData);
  487. this.onLoad(this.page);
  488. this.$message({
  489. type: "success",
  490. message: "操作成功!"
  491. });
  492. done();
  493. } catch (error) {
  494. console.error('更新失败:', error);
  495. this.$message.error('更新失败');
  496. loading();
  497. }
  498. },
  499. /**
  500. * 重置搜索
  501. * @returns {void}
  502. */
  503. searchReset() {
  504. this.query = {};
  505. this.onLoad(this.page);
  506. },
  507. /**
  508. * 搜索变化
  509. * @param {QueryParams} params - 搜索参数
  510. * @param {Function} done - 完成回调
  511. * @returns {void}
  512. */
  513. searchChange(params, done) {
  514. this.query = params;
  515. this.page.currentPage = 1;
  516. this.onLoad(this.page, params);
  517. done();
  518. },
  519. /**
  520. * 选择变化
  521. * @param {NoticeItem[]} list - 选中的数据列表
  522. * @returns {void}
  523. */
  524. selectionChange(list) {
  525. this.selectionList = list;
  526. },
  527. /**
  528. * 清空选择
  529. * @returns {void}
  530. */
  531. selectionClear() {
  532. this.selectionList = [];
  533. this.$refs.crud.toggleSelection();
  534. },
  535. /**
  536. * 打开前回调
  537. * @async
  538. * @param {Function} done - 完成回调
  539. * @param {string} type - 操作类型
  540. * @returns {Promise<void>}
  541. */
  542. async beforeOpen(done, type) {
  543. if (["edit", "view"].includes(type)) {
  544. try {
  545. const res = await getAnnouncement(this.form.id);
  546. this.form = res.data.data;
  547. } catch (error) {
  548. console.error('获取详情失败:', error);
  549. }
  550. } else if (type === "add") {
  551. // 新增时设置默认值
  552. this.form = {
  553. orgId: this.userInfo.orgId || 1,
  554. orgCode: this.userInfo.orgCode || 'ORG_0001',
  555. orgName: this.userInfo.orgName || '默认组织',
  556. visibleRoles: '2', // 默认普通用户
  557. brandScope: {},
  558. customerBlacklist: {},
  559. remark: ''
  560. };
  561. }
  562. done();
  563. },
  564. /**
  565. * 当前页变化
  566. * @param {number} currentPage - 当前页码
  567. * @returns {void}
  568. */
  569. currentChange(currentPage) {
  570. this.page.currentPage = currentPage;
  571. },
  572. /**
  573. * 页大小变化
  574. * @param {number} pageSize - 页大小
  575. * @returns {void}
  576. */
  577. sizeChange(pageSize) {
  578. this.page.pageSize = pageSize;
  579. },
  580. /**
  581. * 刷新变化
  582. * @returns {void}
  583. */
  584. refreshChange() {
  585. this.onLoad(this.page, this.query);
  586. },
  587. /**
  588. * 加载数据
  589. * @async
  590. * @param {PageInfo} page - 分页信息
  591. * @param {QueryParams} [params={}] - 查询参数
  592. * @returns {Promise<void>}
  593. */
  594. async onLoad(page, params = {}) {
  595. this.loading = true;
  596. try {
  597. const queryParams = {
  598. ...params,
  599. ...this.query
  600. };
  601. const res = await getList(page.currentPage, page.pageSize, queryParams);
  602. const data = res.data.data;
  603. this.page.total = data.total;
  604. this.data = data.records;
  605. this.loading = false;
  606. this.selectionClear();
  607. } catch (error) {
  608. console.error('加载数据失败:', error);
  609. this.$message.error('加载数据失败');
  610. this.loading = false;
  611. }
  612. }
  613. }
  614. };
  615. </script>
  616. <style scoped>
  617. .detail-content {
  618. padding: 20px;
  619. }
  620. .detail-info {
  621. margin: 20px 0;
  622. padding: 15px;
  623. background-color: #f5f5f5;
  624. border-radius: 4px;
  625. }
  626. .detail-info p {
  627. margin: 8px 0;
  628. }
  629. .detail-body {
  630. margin-top: 20px;
  631. padding: 15px;
  632. border: 1px solid #e4e7ed;
  633. border-radius: 4px;
  634. min-height: 200px;
  635. }
  636. </style>