announcementIndex.js 34 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912
  1. // @ts-check
  2. /**
  3. * @this {import('@/views/announcement/types').AnnouncementComponent & Vue}
  4. */
  5. /**
  6. * @typedef {import("@/api/announcement").NoticeRecord} NoticeRecord
  7. */
  8. import { getList, update, add, getAnnouncement, getCategoryList, deleteNotice } from "@/api/announcement";
  9. import { getCustomerList } from "@/api/common/index";
  10. import { mapGetters } from "vuex";
  11. import {
  12. ROLE_OPTIONS,
  13. STATUS_OPTIONS,
  14. ANNOUNCEMENT_STATUS,
  15. ROLE_TYPES,
  16. getRoleLabel,
  17. getRoleTagType,
  18. getStatusLabel,
  19. getStatusTagType,
  20. calculateRolesMask,
  21. parseRolesMask,
  22. getVisibleRolesText,
  23. getVisibleRolesTextArray
  24. } from '@/views/announcement/constants';
  25. /**
  26. * 角色类型定义
  27. * @typedef {import('@/views/announcement/constants').RoleType} RoleType
  28. */
  29. /**
  30. * 可见角色掩码类型定义
  31. * @typedef {import('@/views/announcement/constants').VisibleRolesMask} VisibleRolesMask
  32. */
  33. /**
  34. * 角色选项类型定义
  35. * @typedef {import('@/views/announcement/constants').RoleOption} RoleOption
  36. */
  37. /**
  38. * 公告状态类型定义
  39. * @typedef {import('@/views/announcement/constants').AnnouncementStatus} AnnouncementStatus
  40. */
  41. /**
  42. * 公告数据项类型定义
  43. * @typedef {import('@/api/types/announcement').NoticeRecord} NoticeItem
  44. */
  45. /**
  46. * 分类选项类型定义
  47. * @typedef {import('@/api/types/announcement').CategoryOption} CategoryOption
  48. */
  49. /**
  50. * 分页信息类型定义
  51. * @typedef {Object} PageInfo
  52. * @property {number} pageSize - 页大小(默认10)
  53. * @property {number} currentPage - 当前页(从1开始)
  54. * @property {number} total - 总记录数
  55. */
  56. /**
  57. * 查询参数类型定义
  58. * @typedef {import('@/api/types/announcement').NoticeQueryParams} QueryParams
  59. */
  60. /**
  61. * 表格配置列定义
  62. * @typedef {Object} TableColumn
  63. * @property {string} label - 列标签
  64. * @property {string} prop - 属性名
  65. * @property {string} [type] - 输入类型
  66. * @property {number} [span] - 栅格占位
  67. * @property {boolean} [search] - 是否可搜索
  68. * @property {boolean} [overHidden] - 是否超出隐藏
  69. * @property {Array<Object>} [rules] - 验证规则
  70. * @property {Array<Object>} [dicData] - 字典数据
  71. * @property {Object} [props] - 属性配置
  72. * @property {boolean} [slot] - 是否使用插槽
  73. * @property {boolean} [addDisplay] - 新增时是否显示
  74. * @property {boolean} [editDisplay] - 编辑时是否显示
  75. * @property {boolean} [hide] - 是否隐藏
  76. * @property {number} [width] - 列宽度
  77. * @property {boolean} [multiple] - 是否多选
  78. * @property {string} [format] - 格式化
  79. * @property {string} [valueFormat] - 值格式化
  80. * @property {boolean} [showColumn] - 是否显示列
  81. * @property {number} [minRows] - 最小行数
  82. * @property {string} [component] - 组件名称
  83. * @property {Object} [options] - 组件选项
  84. */
  85. /**
  86. * 表格配置选项
  87. * @typedef {Object} TableOption
  88. * @property {string} height - 表格高度
  89. * @property {number} calcHeight - 计算高度
  90. * @property {number} dialogWidth - 对话框宽度
  91. * @property {number} labelWidth - 标签宽度
  92. * @property {boolean} tip - 是否显示提示
  93. * @property {boolean} searchShow - 是否显示搜索
  94. * @property {number} searchMenuSpan - 搜索菜单占位
  95. * @property {boolean} border - 是否显示边框
  96. * @property {boolean} index - 是否显示序号
  97. * @property {boolean} viewBtn - 是否显示查看按钮
  98. * @property {boolean} selection - 是否显示选择框
  99. * @property {boolean} excelBtn - 是否显示导出按钮
  100. * @property {boolean} columnBtn - 是否显示列设置按钮
  101. * @property {boolean} delBtn - 是否显示删除按钮
  102. * @property {boolean} dialogClickModal - 对话框点击遮罩是否关闭
  103. * @property {Array<TableColumn>} column - 列配置
  104. */
  105. /**
  106. * 公告管理混入
  107. * @mixin AnnouncementIndexMixin
  108. */
  109. /**
  110. * 客户黑名单选项类型定义
  111. * @typedef {import('@/api/types/announcement').CustomerBlacklistItem} CustomerBlacklistOption
  112. */
  113. export default {
  114. name: 'AnnouncementIndexMixin',
  115. data() {
  116. return {
  117. /** @type {Partial<NoticeRecord>} 表单数据 */
  118. form: {},
  119. /** @type {Partial<QueryParams>} 查询参数 */
  120. query: {},
  121. /** @type {boolean} 加载状态 */
  122. loading: true,
  123. /** @type {boolean} 详情对话框显示状态 */
  124. detailVisible: false,
  125. /** 用于全屏预览的可见性控制 */
  126. announcementPreviewVisible: false,
  127. /** @type {Partial<NoticeRecord>} 当前查看的详情数据 */
  128. currentDetail: {},
  129. /** @type {PageInfo} 分页信息 */
  130. page: {
  131. pageSize: 10,
  132. currentPage: 1,
  133. total: 0
  134. },
  135. /** @type {Array<NoticeItem>} 选中的数据列表 */
  136. selectionList: [],
  137. /** @type {Array<CategoryOption>} 分类选项列表 */
  138. categoryOptions: [],
  139. /** @type {Array<RoleOption>} 角色选项列表 */
  140. roleOptions: ROLE_OPTIONS,
  141. /** @type {Array<CustomerBlacklistOption>} 客户黑名单选项列表 */
  142. customerBlacklistOptions: [],
  143. /** @type {Array<import("@/api/types/announcement").CustomerBlacklistItem>} 当前客户黑名单 */
  144. currentCustomerBlacklist: [],
  145. /** @type {boolean} 客户选项加载状态 */
  146. customerOptionsLoading: false,
  147. /** @type {boolean} 公告表单组件显示状态 */
  148. announcementFormVisible: false,
  149. /** @type {boolean} 是否为编辑模式 */
  150. isEditMode: false,
  151. /** @type {string|null} 编辑的公告ID */
  152. editAnnouncementId: null,
  153. /** @type {TableOption} 表格配置选项 */
  154. option: {
  155. height: 'auto',
  156. calcHeight: 30,
  157. dialogWidth: 1000,
  158. labelWidth: 120,
  159. tip: false,
  160. searchShow: true,
  161. searchMenuSpan: 6,
  162. border: true,
  163. index: true,
  164. viewBtn: false,
  165. selection: true,
  166. excelBtn: false,
  167. columnBtn: false,
  168. delBtn: false,
  169. dialogClickModal: false,
  170. column: [
  171. {
  172. label: "公告标题",
  173. prop: "title",
  174. span: 12,
  175. search: true,
  176. overHidden: true,
  177. rules: [{
  178. required: true,
  179. message: "请输入公告标题",
  180. trigger: "blur"
  181. }]
  182. },
  183. {
  184. label: "分类",
  185. prop: "categoryName",
  186. type: "select",
  187. dicData: [],
  188. props: {
  189. label: "name",
  190. value: "name"
  191. },
  192. slot: true,
  193. search: true,
  194. span: 12,
  195. searchProp: "categoryId",
  196. rules: [{
  197. required: true,
  198. message: "请选择分类",
  199. trigger: "change"
  200. }]
  201. },
  202. {
  203. label: "组织名称",
  204. prop: "orgName",
  205. span: 12,
  206. overHidden: true,
  207. rules: [{
  208. required: true,
  209. message: "请输入组织名称",
  210. trigger: "blur"
  211. }]
  212. },
  213. {
  214. label: "组织ID",
  215. prop: "orgId",
  216. span: 12,
  217. type: "number",
  218. rules: [{
  219. required: true,
  220. message: "请输入组织ID",
  221. trigger: "blur"
  222. }]
  223. },
  224. {
  225. label: "组织编码",
  226. prop: "orgCode",
  227. span: 12,
  228. rules: [{
  229. required: true,
  230. message: "请输入组织编码",
  231. trigger: "blur"
  232. },{
  233. pattern: /^[A-Za-z0-9_-]+$/,
  234. message: "组织编码只能包含大写字母、数字、下划线和中横线",
  235. trigger: "blur"
  236. }]
  237. },
  238. {
  239. label: "可见角色",
  240. prop: "visibleRoles",
  241. type: "select",
  242. multiple: true,
  243. dicData: ROLE_OPTIONS,
  244. slot: true,
  245. span: 12,
  246. props: {
  247. label: "label",
  248. value: "value"
  249. },
  250. rules: [{
  251. required: true,
  252. message: "请选择可见角色",
  253. trigger: "change"
  254. }]
  255. },
  256. {
  257. label: "状态",
  258. prop: "status",
  259. type: "select",
  260. dicData: STATUS_OPTIONS,
  261. slot: true,
  262. addDisplay: true,
  263. editDisplay: true,
  264. // width: 80
  265. },
  266. {
  267. label: "创建时间",
  268. prop: "createTime",
  269. type: "datetime",
  270. format: "yyyy-MM-dd HH:mm:ss",
  271. valueFormat: "yyyy-MM-dd HH:mm:ss",
  272. addDisplay: false,
  273. editDisplay: false,
  274. overHidden: true,
  275. width: 150
  276. },
  277. {
  278. label: "分类名称",
  279. prop: "categoryName",
  280. hide: true,
  281. addDisplay: false,
  282. editDisplay: false
  283. },
  284. {
  285. label: "备注",
  286. prop: "remark",
  287. type: "textarea",
  288. span: 24,
  289. minRows: 3,
  290. hide: true
  291. },
  292. {
  293. label: "公告内容",
  294. prop: "content",
  295. component: 'AvueUeditor',
  296. options: {
  297. action: '/api/blade-resource/oss/endpoint/put-file',
  298. props: {
  299. res: "data",
  300. url: "link",
  301. }
  302. },
  303. showColumn: false,
  304. hide: true,
  305. minRows: 6,
  306. span: 24,
  307. rules: [{
  308. required: true,
  309. message: "请输入公告内容",
  310. trigger: "blur"
  311. }]
  312. },
  313. {
  314. label: "品牌范围",
  315. prop: "brandScope",
  316. type: "json",
  317. hide: true,
  318. span: 24
  319. },
  320. {
  321. label: "客户黑名单",
  322. prop: "customerBlacklist",
  323. slot: true,
  324. hide: true,
  325. span: 24
  326. }
  327. ]
  328. },
  329. /** @type {Array<NoticeItem>} 表格数据 */
  330. data: []
  331. };
  332. },
  333. computed: {
  334. ...mapGetters(["permission", "userInfo"]),
  335. /**
  336. * 权限列表
  337. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  338. * @returns {Object} 权限配置对象
  339. */
  340. permissionList() {
  341. return {
  342. addBtn: true,
  343. viewBtn: false,
  344. delBtn: false,
  345. editBtn: true,
  346. };
  347. },
  348. /**
  349. * 选中的ID字符串
  350. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  351. * @returns {string} 逗号分隔的ID字符串
  352. */
  353. ids() {
  354. /** @type {Array<string>} */
  355. const ids = [];
  356. this.selectionList.forEach((/** @type {NoticeItem} */ ele) => {
  357. ids.push(ele.id);
  358. });
  359. return ids.join(",");
  360. }
  361. },
  362. /**
  363. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  364. */
  365. created() {
  366. this.loadCategoryOptions();
  367. },
  368. watch: {
  369. announcementFormVisible(val) {
  370. if (val === true) {
  371. // 打开表单时,立即重新加载分类列表(表单与列表都会受益)
  372. this.loadCategoryOptions();
  373. } else {
  374. // 关闭表单后,avue-crud 会通过 v-if 重新渲染,需等 DOM 恢复后再刷新字典
  375. this.$nextTick(() => this.loadCategoryOptions());
  376. }
  377. },
  378. announcementPreviewVisible(val) {
  379. if (val === true) {
  380. // 打开预览时不需要刷新列表
  381. } else {
  382. // 关闭预览后,avue-crud 会通过 v-if 重新渲染,需等 DOM 恢复后再刷新字典
  383. this.$nextTick(() => this.loadCategoryOptions());
  384. }
  385. }
  386. },
  387. methods: {
  388. // 导入常量工具函数
  389. calculateRolesMask,
  390. parseRolesMask,
  391. getVisibleRolesText,
  392. getVisibleRolesTextArray,
  393. getRoleLabel,
  394. getRoleTagType,
  395. getStatusTagType,
  396. getStatusLabel,
  397. getStatusText: getStatusLabel,
  398. // 页面显示时触发:刷新分类列表(最小改动)
  399. onShow() {
  400. // 打印触发标识,便于排查
  401. console.log('[公告] onShow 勾子触发');
  402. // 重新加载分类下拉数据
  403. this.loadCategoryOptions();
  404. },
  405. /**
  406. * 加载分类选项
  407. * @async
  408. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  409. * @returns {Promise<void>}
  410. * @throws {Error} 当加载分类选项失败时抛出错误
  411. */
  412. async loadCategoryOptions() {
  413. try {
  414. const response = await getCategoryList();
  415. const categoryData = response.data?.data || [];
  416. /** @type {Array<CategoryOption>} */
  417. this.categoryOptions = categoryData
  418. .filter(item => item.status === 1 && item.isDeleted === 0)
  419. .map(item => ({
  420. id: item.id,
  421. name: item.name,
  422. value: item.name,
  423. label: item.name,
  424. orgId: item.orgId,
  425. orgName: item.orgName,
  426. sortOrder: item.sortOrder || 0
  427. }))
  428. .sort((a, b) => a.sortOrder - b.sortOrder);
  429. // 更新表格列配置中的字典数据
  430. const categoryColumn = this.option.column.find((/** @type {TableColumn} */ col) => col.prop === 'categoryName');
  431. if (categoryColumn) {
  432. categoryColumn.dicData = this.categoryOptions;
  433. // 重新初始化 avue-crud 的字典,确保搜索下拉及时更新
  434. if (this.$refs && this.$refs.crud && typeof this.$refs.crud.dicInit === 'function') {
  435. this.$nextTick(() => this.$refs.crud.dicInit());
  436. }
  437. }
  438. } catch (error) {
  439. console.error('加载分类选项失败:', error);
  440. this.$message.error('加载分类选项失败,使用默认分类');
  441. // 使用默认分类选项
  442. /** @type {Array<CategoryOption>} */
  443. this.categoryOptions = [
  444. { id: 1, name: '系统公告', value: '系统公告', label: '系统公告', sortOrder: 0 },
  445. { id: 2, name: '部门公告', value: '部门公告', label: '部门公告', sortOrder: 1 }
  446. ];
  447. const categoryColumn = this.option.column.find((/** @type {TableColumn} */ col) => col.prop === 'categoryName');
  448. if (categoryColumn) {
  449. categoryColumn.dicData = this.categoryOptions;
  450. // 重新初始化 avue-crud 的字典,确保搜索下拉及时更新
  451. if (this.$refs && this.$refs.crud && typeof this.$refs.crud.dicInit === 'function') {
  452. this.$nextTick(() => this.$refs.crud.dicInit());
  453. }
  454. }
  455. }
  456. },
  457. /**
  458. * 查看详情(支持行内操作)- 改为进入全屏预览模式
  459. * @async
  460. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  461. * @param {Partial<NoticeRecord>} [row] - 可选,当前行数据
  462. * @returns {Promise<void>}
  463. * @throws {Error} 当获取详情失败时抛出错误
  464. */
  465. async handleDetail(row) {
  466. const id = row && row.id ? row.id : (this.selectionList[0] && this.selectionList[0].id);
  467. if (!id) {
  468. this.$message.warning("请选择一条数据查看详情");
  469. return;
  470. }
  471. try {
  472. const response = await getAnnouncement(id);
  473. this.currentDetail = response.data?.data || {};
  474. // 打开全屏预览
  475. this.announcementPreviewVisible = true;
  476. } catch (error) {
  477. console.error('获取详情失败:', error);
  478. this.$message.error('获取详情失败,请稍后重试');
  479. }
  480. },
  481. /**
  482. * 关闭全屏预览并返回列表
  483. */
  484. handlePreviewBack() {
  485. this.announcementPreviewVisible = false;
  486. // 可按需清理当前详情
  487. // this.currentDetail = {};
  488. },
  489. // 新增:单行删除(操作列按钮)
  490. /**
  491. * 行删除(操作列按钮)
  492. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  493. * @param {Partial<NoticeItem>} row - 当前行数据
  494. * @returns {Promise<void>}
  495. */
  496. async handleRowDelete(row) {
  497. const id = row && row.id;
  498. if (!id) {
  499. this.$message.warning("缺少公告ID,无法删除");
  500. return;
  501. }
  502. try {
  503. await this.$confirm("确定将选择数据删除?", {
  504. confirmButtonText: "确定",
  505. cancelButtonText: "取消",
  506. type: "warning"
  507. });
  508. await deleteNotice(id);
  509. this.$message({
  510. type: "success",
  511. message: "操作成功!"
  512. });
  513. // 刷新列表
  514. this.onLoad(this.page, this.query);
  515. } catch (error) {
  516. if (error !== 'cancel') {
  517. console.error('删除公告失败:', error);
  518. this.$message({
  519. type: "error",
  520. message: "删除失败,请稍后重试"
  521. });
  522. } else {
  523. this.$message({
  524. type: "info",
  525. message: "已取消删除"
  526. });
  527. }
  528. }
  529. },
  530. /**
  531. * 保存行数据
  532. * @async
  533. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  534. * @param {NoticeItem} row - 行数据
  535. * @param {Function} done - 完成回调
  536. * @param {Function} loading - 加载回调
  537. * @returns {Promise<void>}
  538. * @throws {Error} 当保存失败时抛出错误
  539. */
  540. async rowSave(row, done, loading) {
  541. // 新增操作现在由表单组件处理,此方法已废弃
  542. console.warn('rowSave方法已废弃,请使用表单组件进行新增操作');
  543. done();
  544. },
  545. /**
  546. * 更新行数据
  547. * @async
  548. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  549. * @param {NoticeItem} row - 行数据
  550. * @param {number} index - 行索引
  551. * @param {Function} done - 完成回调
  552. * @param {Function} loading - 加载回调
  553. * @returns {Promise<void>}
  554. * @throws {Error} 当更新失败时抛出错误
  555. */
  556. async rowUpdate(row, index, done, loading) {
  557. // 编辑操作现在由表单组件处理,此方法已废弃
  558. console.warn('rowUpdate方法已废弃,请使用表单组件进行编辑操作');
  559. done();
  560. },
  561. /**
  562. * 搜索变化事件
  563. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  564. * @param {QueryParams} params - 搜索参数
  565. * @param {Function} done - 完成回调
  566. */
  567. searchChange(params, done) {
  568. /** @type {QueryParams} */
  569. this.query = params || {};
  570. this.onLoad(this.page, this.query);
  571. done();
  572. },
  573. /**
  574. * 搜索重置事件
  575. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  576. */
  577. searchReset() {
  578. /** @type {QueryParams} */
  579. this.query = {};
  580. this.onLoad(this.page);
  581. },
  582. /**
  583. * 选择变化事件
  584. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  585. * @param {Array<NoticeItem>} list - 选中的数据列表
  586. */
  587. selectionChange(list) {
  588. /** @type {Array<NoticeItem>} */
  589. this.selectionList = Array.isArray(list) ? list : [];
  590. },
  591. /**
  592. * 当前页变化事件
  593. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  594. * @param {number} currentPage - 当前页码(从1开始)
  595. */
  596. currentChange(currentPage) {
  597. if (typeof currentPage === 'number' && currentPage > 0) {
  598. this.page.currentPage = currentPage;
  599. }
  600. },
  601. /**
  602. * 页大小变化事件
  603. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  604. * @param {number} pageSize - 页大小
  605. */
  606. sizeChange(pageSize) {
  607. if (typeof pageSize === 'number' && pageSize > 0) {
  608. this.page.pageSize = pageSize;
  609. }
  610. },
  611. /**
  612. * 刷新变化事件
  613. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  614. */
  615. refreshChange() {
  616. // 刷新列表与分类字典
  617. this.loadCategoryOptions();
  618. this.onLoad(this.page, this.query);
  619. },
  620. /**
  621. * 打开前回调
  622. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  623. * @async
  624. * @param {Function} done - 完成回调
  625. * @param {string} type - 操作类型(add/edit/view)
  626. * @returns {Promise<void>}
  627. * @throws {Error} 当获取详情失败时抛出错误
  628. */
  629. async beforeOpen(done, type) {
  630. // 对于新增和编辑操作,使用表单组件
  631. if (type === "add") {
  632. this.isEditMode = false;
  633. this.editAnnouncementId = null;
  634. this.announcementFormVisible = true;
  635. done();
  636. return;
  637. } else if (type === "edit") {
  638. this.isEditMode = true;
  639. this.editAnnouncementId = this.form?.id || null;
  640. this.announcementFormVisible = true;
  641. done();
  642. return;
  643. }
  644. // 对于查看操作,保持原有逻辑
  645. if (type === "view") {
  646. try {
  647. if (!this.form?.id) {
  648. this.$message.error('缺少公告ID,无法获取详情');
  649. done();
  650. return;
  651. }
  652. const response = await getAnnouncement(this.form.id);
  653. const formData = response.data?.data || {};
  654. /** @type {{visibleRoles: Array<RoleType>, customerBlackList: Array<import("@/views/announcement/types").CustomerBlacklistItem>, BrandScope: Array<import("@/api/types/announcement").BrandScopeItem>}} */
  655. const convertType = {
  656. visibleRoles: [],
  657. customerBlackList: [],
  658. BrandScope: [],
  659. }
  660. // 将掩码值转换为数值数组格式供表单使用
  661. if (typeof formData.visibleRoles === 'number') {
  662. const roleObjects = this.parseRolesMask(formData.visibleRoles || 0);
  663. convertType.visibleRoles = roleObjects.map(role => role.value);
  664. } else if (typeof formData.visibleRoles === 'string') {
  665. const roleValues = formData.visibleRoles ? parseInt(formData.visibleRoles) : 0;
  666. let roleObjects = this.parseRolesMask(roleValues)
  667. convertType.visibleRoles = roleObjects.map(role => role.value);
  668. } else {
  669. convertType.visibleRoles = [];
  670. }
  671. // 确保客户黑名单是数组格式
  672. /** @type {Array<import("@/views/announcement/types").CustomerBlacklistItem>} */
  673. const tmpCustomerData = JSON.parse(formData.customerBlacklist || '[]')
  674. convertType.customerBlackList = tmpCustomerData.map(item => ({
  675. ...item,
  676. }))
  677. // 如果有客户黑名单数据,设置到选项中以便显示
  678. if (convertType.customerBlackList.length > 0) {
  679. /** @type {Array<CustomerBlacklistOption>} */
  680. this.customerBlacklistOptions = convertType.customerBlackList.map(item => ({
  681. id: item.ID,
  682. Customer_NAME: item.NAME,
  683. Customer_CODE: item.CODE,
  684. }))
  685. }
  686. /** @type {Array<import("@/api/types/announcement").BrandScopeItem>} */
  687. const tmpBrandData = JSON.parse(formData.brandScope || '[]')
  688. convertType.BrandScope = tmpBrandData.map(item => ({
  689. ...item,
  690. }))
  691. this.form = {
  692. ...formData,
  693. ...convertType,
  694. };
  695. } catch (error) {
  696. console.error('获取详情失败:', error);
  697. this.$message.error('获取详情失败,请稍后重试');
  698. }
  699. }
  700. done();
  701. },
  702. /**
  703. * 加载数据
  704. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  705. * @async
  706. * @param {PageInfo} page - 分页信息
  707. * @param {QueryParams} [params={}] - 查询参数
  708. * @returns {Promise<void>}
  709. * @throws {Error} 当加载数据失败时抛出错误
  710. */
  711. async onLoad(page, params = {}) {
  712. this.loading = true;
  713. try {
  714. // 参数验证
  715. if (!page || typeof page.currentPage !== 'number' || typeof page.pageSize !== 'number') {
  716. throw new Error('分页参数无效');
  717. }
  718. /** @type {QueryParams} */
  719. const query = {
  720. ...params,
  721. current: page.currentPage,
  722. size: page.pageSize
  723. };
  724. const response = await getList(page.currentPage, page.pageSize, query);
  725. const data = response.data.data;
  726. if (!data) {
  727. throw new Error('响应数据格式错误');
  728. }
  729. this.page.total = data.total || 0;
  730. /** @type {Array<NoticeItem>} */
  731. this.data = Array.isArray(data.records) ? data.records : [];
  732. } catch (error) {
  733. console.error('加载数据失败:', error);
  734. this.$message.error(`加载数据失败: ${error.message || '未知错误'}`);
  735. // 设置默认值避免页面异常
  736. this.page.total = 0;
  737. /** @type {Array<NoticeItem>} */
  738. this.data = [];
  739. } finally {
  740. this.loading = false;
  741. }
  742. },
  743. /**
  744. * 远程搜索客户数据
  745. * @async
  746. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  747. * @param {string} query - 搜索关键词
  748. * @returns {Promise<void>}
  749. * @throws {Error} 当搜索客户失败时抛出错误
  750. */
  751. async remoteSearchCustomers(query) {
  752. if (!query || typeof query !== 'string' || query.trim().length < 1) {
  753. /** @type {Array<CustomerBlacklistOption>} */
  754. this.customerBlacklistOptions = [];
  755. return;
  756. }
  757. this.customerOptionsLoading = true;
  758. try {
  759. const response = await getCustomerList(1, 50, {
  760. customerName: query.trim()
  761. });
  762. if (response.data && response.data.success && response.data.data) {
  763. const customers = response.data.data.records || [];
  764. this.currentCustomerBlacklist = customers.map(c => ({
  765. id: c.Customer_ID,
  766. Customer_NAME: c.Customer_NAME,
  767. Customer_CODE: c.Customer_CODE,
  768. Customer_ShortName: c.Customer_ShortName
  769. }))
  770. this.customerBlacklistOptions = customers.map(customer => ({
  771. id: customer.Customer_ID,
  772. Customer_NAME: customer.Customer_NAME,
  773. Customer_CODE: customer.Customer_CODE,
  774. Customer_ShortName: customer.Customer_ShortName
  775. }));
  776. } else {
  777. /** @type {Array<CustomerBlacklistOption>} */
  778. this.customerBlacklistOptions = [];
  779. }
  780. } catch (error) {
  781. console.error('获取客户列表失败:', error);
  782. this.$message.error('获取客户列表失败,请稍后重试');
  783. /** @type {Array<CustomerBlacklistOption>} */
  784. this.customerBlacklistOptions = [];
  785. } finally {
  786. this.customerOptionsLoading = false;
  787. }
  788. },
  789. /**
  790. * 客户黑名单选择变化处理
  791. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  792. * @param {Array<CustomerBlacklistOption>} selectedCustomers - 选中的客户列表
  793. */
  794. handleCustomerBlacklistChange(selectedCustomers) {
  795. if (!Array.isArray(selectedCustomers)) {
  796. /** @type {Array<CustomerBlacklistOption>} */
  797. this.form.customerBlacklist = [];
  798. return;
  799. }
  800. /** @type {Array<CustomerBlacklistOption>} */
  801. this.form.customerBlacklist = selectedCustomers;
  802. },
  803. /**
  804. * 获取客户显示名称
  805. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  806. * @param {CustomerBlacklistOption} customer - 客户对象
  807. * @returns {string} 显示名称
  808. */
  809. getCustomerDisplayName(customer) {
  810. if (!customer || typeof customer !== 'object') {
  811. return '';
  812. }
  813. const name = customer.Customer_NAME || '';
  814. const code = customer.Customer_CODE || '';
  815. return code ? `${name}(${code})` : name;
  816. },
  817. /**
  818. * 清空客户搜索结果
  819. * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default}
  820. */
  821. clearCustomerOptions() {
  822. this.customerBlacklistOptions = [];
  823. },
  824. /**
  825. * 处理表单组件返回事件
  826. */
  827. handleFormBack() {
  828. this.announcementFormVisible = false;
  829. this.isEditMode = false;
  830. this.editAnnouncementId = null;
  831. },
  832. /**
  833. * 处理表单保存成功事件
  834. */
  835. handleFormSaveSuccess() {
  836. this.announcementFormVisible = false;
  837. this.isEditMode = false;
  838. this.editAnnouncementId = null;
  839. // 刷新分类字典
  840. this.loadCategoryOptions();
  841. // 刷新列表数据
  842. this.onLoad(this.page);
  843. }
  844. }
  845. };