// @ts-check /** * @this {import('@/views/announcement/types').AnnouncementComponent & Vue} */ /** * @typedef {import("@/api/announcement").NoticeRecord} NoticeRecord */ import { getList, update, add, getAnnouncement, getCategoryList, deleteNotice } from "@/api/announcement"; import { getCustomerList } from "@/api/common/index"; import { mapGetters } from "vuex"; import { ROLE_OPTIONS, STATUS_OPTIONS, ANNOUNCEMENT_STATUS, ROLE_TYPES, getRoleLabel, getRoleTagType, getStatusLabel, getStatusTagType, calculateRolesMask, parseRolesMask, getVisibleRolesText, getVisibleRolesTextArray } from '@/views/announcement/constants'; /** * 角色类型定义 * @typedef {import('@/views/announcement/constants').RoleType} RoleType */ /** * 可见角色掩码类型定义 * @typedef {import('@/views/announcement/constants').VisibleRolesMask} VisibleRolesMask */ /** * 角色选项类型定义 * @typedef {import('@/views/announcement/constants').RoleOption} RoleOption */ /** * 公告状态类型定义 * @typedef {import('@/views/announcement/constants').AnnouncementStatus} AnnouncementStatus */ /** * 公告数据项类型定义 * @typedef {import('@/api/types/announcement').NoticeRecord} NoticeItem */ /** * 分类选项类型定义 * @typedef {import('@/api/types/announcement').CategoryOption} CategoryOption */ /** * 分页信息类型定义 * @typedef {Object} PageInfo * @property {number} pageSize - 页大小(默认10) * @property {number} currentPage - 当前页(从1开始) * @property {number} total - 总记录数 */ /** * 查询参数类型定义 * @typedef {import('@/api/types/announcement').NoticeQueryParams} QueryParams */ /** * 表格配置列定义 * @typedef {Object} TableColumn * @property {string} label - 列标签 * @property {string} prop - 属性名 * @property {string} [type] - 输入类型 * @property {number} [span] - 栅格占位 * @property {boolean} [search] - 是否可搜索 * @property {boolean} [overHidden] - 是否超出隐藏 * @property {Array} [rules] - 验证规则 * @property {Array} [dicData] - 字典数据 * @property {Object} [props] - 属性配置 * @property {boolean} [slot] - 是否使用插槽 * @property {boolean} [addDisplay] - 新增时是否显示 * @property {boolean} [editDisplay] - 编辑时是否显示 * @property {boolean} [hide] - 是否隐藏 * @property {number} [width] - 列宽度 * @property {boolean} [multiple] - 是否多选 * @property {string} [format] - 格式化 * @property {string} [valueFormat] - 值格式化 * @property {boolean} [showColumn] - 是否显示列 * @property {number} [minRows] - 最小行数 * @property {string} [component] - 组件名称 * @property {Object} [options] - 组件选项 */ /** * 表格配置选项 * @typedef {Object} TableOption * @property {string} height - 表格高度 * @property {number} calcHeight - 计算高度 * @property {number} dialogWidth - 对话框宽度 * @property {number} labelWidth - 标签宽度 * @property {boolean} tip - 是否显示提示 * @property {boolean} searchShow - 是否显示搜索 * @property {number} searchMenuSpan - 搜索菜单占位 * @property {boolean} border - 是否显示边框 * @property {boolean} index - 是否显示序号 * @property {boolean} viewBtn - 是否显示查看按钮 * @property {boolean} selection - 是否显示选择框 * @property {boolean} excelBtn - 是否显示导出按钮 * @property {boolean} columnBtn - 是否显示列设置按钮 * @property {boolean} delBtn - 是否显示删除按钮 * @property {boolean} dialogClickModal - 对话框点击遮罩是否关闭 * @property {Array} column - 列配置 */ /** * 公告管理混入 * @mixin AnnouncementIndexMixin */ /** * 客户黑名单选项类型定义 * @typedef {import('@/api/types/announcement').CustomerBlacklistItem} CustomerBlacklistOption */ export default { name: 'AnnouncementIndexMixin', data() { return { /** @type {Partial} 表单数据 */ form: {}, /** @type {Partial} 查询参数 */ query: {}, /** @type {boolean} 加载状态 */ loading: true, /** @type {boolean} 详情对话框显示状态 */ detailVisible: false, /** 用于全屏预览的可见性控制 */ announcementPreviewVisible: false, /** @type {Partial} 当前查看的详情数据 */ currentDetail: {}, /** @type {PageInfo} 分页信息 */ page: { pageSize: 10, currentPage: 1, total: 0 }, /** @type {Array} 选中的数据列表 */ selectionList: [], /** @type {Array} 分类选项列表 */ categoryOptions: [], /** @type {Array} 角色选项列表 */ roleOptions: ROLE_OPTIONS, /** @type {Array} 客户黑名单选项列表 */ customerBlacklistOptions: [], /** @type {Array} 当前客户黑名单 */ currentCustomerBlacklist: [], /** @type {boolean} 客户选项加载状态 */ customerOptionsLoading: false, /** @type {boolean} 公告表单组件显示状态 */ announcementFormVisible: false, /** @type {boolean} 是否为编辑模式 */ isEditMode: false, /** @type {string|null} 编辑的公告ID */ editAnnouncementId: null, /** @type {TableOption} 表格配置选项 */ option: { height: 'auto', calcHeight: 30, dialogWidth: 1000, labelWidth: 120, tip: false, searchShow: true, searchMenuSpan: 6, border: true, index: true, viewBtn: false, selection: true, excelBtn: false, columnBtn: false, delBtn: false, dialogClickModal: false, column: [ { label: "公告标题", prop: "title", span: 12, search: true, overHidden: true, rules: [{ required: true, message: "请输入公告标题", trigger: "blur" }] }, { label: "分类", prop: "categoryName", type: "select", dicData: [], props: { label: "name", value: "name" }, slot: true, search: true, span: 12, searchProp: "categoryId", rules: [{ required: true, message: "请选择分类", trigger: "change" }] }, { label: "组织名称", prop: "orgName", span: 12, overHidden: true, rules: [{ required: true, message: "请输入组织名称", trigger: "blur" }] }, { label: "组织ID", prop: "orgId", span: 12, type: "number", rules: [{ required: true, message: "请输入组织ID", trigger: "blur" }] }, { label: "组织编码", prop: "orgCode", span: 12, rules: [{ required: true, message: "请输入组织编码", trigger: "blur" },{ pattern: /^[A-Za-z0-9_-]+$/, message: "组织编码只能包含大写字母、数字、下划线和中横线", trigger: "blur" }] }, { label: "可见角色", prop: "visibleRoles", type: "select", multiple: true, dicData: ROLE_OPTIONS, slot: true, span: 12, props: { label: "label", value: "value" }, rules: [{ required: true, message: "请选择可见角色", trigger: "change" }] }, { label: "状态", prop: "status", type: "select", dicData: STATUS_OPTIONS, slot: true, addDisplay: true, editDisplay: true, // width: 80 }, { label: "创建时间", prop: "createTime", type: "datetime", format: "yyyy-MM-dd HH:mm:ss", valueFormat: "yyyy-MM-dd HH:mm:ss", addDisplay: false, editDisplay: false, overHidden: true, width: 150 }, { label: "分类名称", prop: "categoryName", hide: true, addDisplay: false, editDisplay: false }, { label: "备注", prop: "remark", type: "textarea", span: 24, minRows: 3, hide: true }, { label: "公告内容", prop: "content", component: 'AvueUeditor', options: { action: '/api/blade-resource/oss/endpoint/put-file', props: { res: "data", url: "link", } }, showColumn: false, hide: true, minRows: 6, span: 24, rules: [{ required: true, message: "请输入公告内容", trigger: "blur" }] }, { label: "品牌范围", prop: "brandScope", type: "json", hide: true, span: 24 }, { label: "客户黑名单", prop: "customerBlacklist", slot: true, hide: true, span: 24 } ] }, /** @type {Array} 表格数据 */ data: [] }; }, computed: { ...mapGetters(["permission", "userInfo"]), /** * 权限列表 * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @returns {Object} 权限配置对象 */ permissionList() { return { addBtn: true, viewBtn: false, delBtn: false, editBtn: true, }; }, /** * 选中的ID字符串 * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @returns {string} 逗号分隔的ID字符串 */ ids() { /** @type {Array} */ const ids = []; this.selectionList.forEach((/** @type {NoticeItem} */ ele) => { ids.push(ele.id); }); return ids.join(","); } }, /** * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} */ created() { this.loadCategoryOptions(); }, watch: { announcementFormVisible(val) { if (val === true) { // 打开表单时,立即重新加载分类列表(表单与列表都会受益) this.loadCategoryOptions(); } else { // 关闭表单后,avue-crud 会通过 v-if 重新渲染,需等 DOM 恢复后再刷新字典 this.$nextTick(() => this.loadCategoryOptions()); } }, announcementPreviewVisible(val) { if (val === true) { // 打开预览时不需要刷新列表 } else { // 关闭预览后,avue-crud 会通过 v-if 重新渲染,需等 DOM 恢复后再刷新字典 this.$nextTick(() => this.loadCategoryOptions()); } } }, methods: { // 导入常量工具函数 calculateRolesMask, parseRolesMask, getVisibleRolesText, getVisibleRolesTextArray, getRoleLabel, getRoleTagType, getStatusTagType, getStatusLabel, getStatusText: getStatusLabel, // 页面显示时触发:刷新分类列表(最小改动) onShow() { // 打印触发标识,便于排查 console.log('[公告] onShow 勾子触发'); // 重新加载分类下拉数据 this.loadCategoryOptions(); }, /** * 加载分类选项 * @async * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @returns {Promise} * @throws {Error} 当加载分类选项失败时抛出错误 */ async loadCategoryOptions() { try { const response = await getCategoryList(); const categoryData = response.data?.data || []; /** @type {Array} */ this.categoryOptions = categoryData .filter(item => item.status === 1 && item.isDeleted === 0) .map(item => ({ id: item.id, name: item.name, value: item.name, label: item.name, orgId: item.orgId, orgName: item.orgName, sortOrder: item.sortOrder || 0 })) .sort((a, b) => a.sortOrder - b.sortOrder); // 更新表格列配置中的字典数据 const categoryColumn = this.option.column.find((/** @type {TableColumn} */ col) => col.prop === 'categoryName'); if (categoryColumn) { categoryColumn.dicData = this.categoryOptions; // 重新初始化 avue-crud 的字典,确保搜索下拉及时更新 if (this.$refs && this.$refs.crud && typeof this.$refs.crud.dicInit === 'function') { this.$nextTick(() => this.$refs.crud.dicInit()); } } } catch (error) { console.error('加载分类选项失败:', error); this.$message.error('加载分类选项失败,使用默认分类'); // 使用默认分类选项 /** @type {Array} */ this.categoryOptions = [ { id: 1, name: '系统公告', value: '系统公告', label: '系统公告', sortOrder: 0 }, { id: 2, name: '部门公告', value: '部门公告', label: '部门公告', sortOrder: 1 } ]; const categoryColumn = this.option.column.find((/** @type {TableColumn} */ col) => col.prop === 'categoryName'); if (categoryColumn) { categoryColumn.dicData = this.categoryOptions; // 重新初始化 avue-crud 的字典,确保搜索下拉及时更新 if (this.$refs && this.$refs.crud && typeof this.$refs.crud.dicInit === 'function') { this.$nextTick(() => this.$refs.crud.dicInit()); } } } }, /** * 查看详情(支持行内操作)- 改为进入全屏预览模式 * @async * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @param {Partial} [row] - 可选,当前行数据 * @returns {Promise} * @throws {Error} 当获取详情失败时抛出错误 */ async handleDetail(row) { const id = row && row.id ? row.id : (this.selectionList[0] && this.selectionList[0].id); if (!id) { this.$message.warning("请选择一条数据查看详情"); return; } try { const response = await getAnnouncement(id); this.currentDetail = response.data?.data || {}; // 打开全屏预览 this.announcementPreviewVisible = true; } catch (error) { console.error('获取详情失败:', error); this.$message.error('获取详情失败,请稍后重试'); } }, /** * 关闭全屏预览并返回列表 */ handlePreviewBack() { this.announcementPreviewVisible = false; // 可按需清理当前详情 // this.currentDetail = {}; }, // 新增:单行删除(操作列按钮) /** * 行删除(操作列按钮) * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @param {Partial} row - 当前行数据 * @returns {Promise} */ async handleRowDelete(row) { const id = row && row.id; if (!id) { this.$message.warning("缺少公告ID,无法删除"); return; } try { await this.$confirm("确定将选择数据删除?", { confirmButtonText: "确定", cancelButtonText: "取消", type: "warning" }); await deleteNotice(id); this.$message({ type: "success", message: "操作成功!" }); // 刷新列表 this.onLoad(this.page, this.query); } catch (error) { if (error !== 'cancel') { console.error('删除公告失败:', error); this.$message({ type: "error", message: "删除失败,请稍后重试" }); } else { this.$message({ type: "info", message: "已取消删除" }); } } }, /** * 保存行数据 * @async * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @param {NoticeItem} row - 行数据 * @param {Function} done - 完成回调 * @param {Function} loading - 加载回调 * @returns {Promise} * @throws {Error} 当保存失败时抛出错误 */ async rowSave(row, done, loading) { // 新增操作现在由表单组件处理,此方法已废弃 console.warn('rowSave方法已废弃,请使用表单组件进行新增操作'); done(); }, /** * 更新行数据 * @async * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @param {NoticeItem} row - 行数据 * @param {number} index - 行索引 * @param {Function} done - 完成回调 * @param {Function} loading - 加载回调 * @returns {Promise} * @throws {Error} 当更新失败时抛出错误 */ async rowUpdate(row, index, done, loading) { // 编辑操作现在由表单组件处理,此方法已废弃 console.warn('rowUpdate方法已废弃,请使用表单组件进行编辑操作'); done(); }, /** * 搜索变化事件 * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @param {QueryParams} params - 搜索参数 * @param {Function} done - 完成回调 */ searchChange(params, done) { /** @type {QueryParams} */ this.query = params || {}; this.onLoad(this.page, this.query); done(); }, /** * 搜索重置事件 * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} */ searchReset() { /** @type {QueryParams} */ this.query = {}; this.onLoad(this.page); }, /** * 选择变化事件 * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @param {Array} list - 选中的数据列表 */ selectionChange(list) { /** @type {Array} */ this.selectionList = Array.isArray(list) ? list : []; }, /** * 当前页变化事件 * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @param {number} currentPage - 当前页码(从1开始) */ currentChange(currentPage) { if (typeof currentPage === 'number' && currentPage > 0) { this.page.currentPage = currentPage; } }, /** * 页大小变化事件 * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @param {number} pageSize - 页大小 */ sizeChange(pageSize) { if (typeof pageSize === 'number' && pageSize > 0) { this.page.pageSize = pageSize; } }, /** * 刷新变化事件 * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} */ refreshChange() { // 刷新列表与分类字典 this.loadCategoryOptions(); this.onLoad(this.page, this.query); }, /** * 打开前回调 * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @async * @param {Function} done - 完成回调 * @param {string} type - 操作类型(add/edit/view) * @returns {Promise} * @throws {Error} 当获取详情失败时抛出错误 */ async beforeOpen(done, type) { // 对于新增和编辑操作,使用表单组件 if (type === "add") { this.isEditMode = false; this.editAnnouncementId = null; this.announcementFormVisible = true; done(); return; } else if (type === "edit") { this.isEditMode = true; this.editAnnouncementId = this.form?.id || null; this.announcementFormVisible = true; done(); return; } // 对于查看操作,保持原有逻辑 if (type === "view") { try { if (!this.form?.id) { this.$message.error('缺少公告ID,无法获取详情'); done(); return; } const response = await getAnnouncement(this.form.id); const formData = response.data?.data || {}; /** @type {{visibleRoles: Array, customerBlackList: Array, BrandScope: Array}} */ const convertType = { visibleRoles: [], customerBlackList: [], BrandScope: [], } // 将掩码值转换为数值数组格式供表单使用 if (typeof formData.visibleRoles === 'number') { const roleObjects = this.parseRolesMask(formData.visibleRoles || 0); convertType.visibleRoles = roleObjects.map(role => role.value); } else if (typeof formData.visibleRoles === 'string') { const roleValues = formData.visibleRoles ? parseInt(formData.visibleRoles) : 0; let roleObjects = this.parseRolesMask(roleValues) convertType.visibleRoles = roleObjects.map(role => role.value); } else { convertType.visibleRoles = []; } // 确保客户黑名单是数组格式 /** @type {Array} */ const tmpCustomerData = JSON.parse(formData.customerBlacklist || '[]') convertType.customerBlackList = tmpCustomerData.map(item => ({ ...item, })) // 如果有客户黑名单数据,设置到选项中以便显示 if (convertType.customerBlackList.length > 0) { /** @type {Array} */ this.customerBlacklistOptions = convertType.customerBlackList.map(item => ({ id: item.ID, Customer_NAME: item.NAME, Customer_CODE: item.CODE, })) } /** @type {Array} */ const tmpBrandData = JSON.parse(formData.brandScope || '[]') convertType.BrandScope = tmpBrandData.map(item => ({ ...item, })) this.form = { ...formData, ...convertType, }; } catch (error) { console.error('获取详情失败:', error); this.$message.error('获取详情失败,请稍后重试'); } } done(); }, /** * 加载数据 * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @async * @param {PageInfo} page - 分页信息 * @param {QueryParams} [params={}] - 查询参数 * @returns {Promise} * @throws {Error} 当加载数据失败时抛出错误 */ async onLoad(page, params = {}) { this.loading = true; try { // 参数验证 if (!page || typeof page.currentPage !== 'number' || typeof page.pageSize !== 'number') { throw new Error('分页参数无效'); } /** @type {QueryParams} */ const query = { ...params, current: page.currentPage, size: page.pageSize }; const response = await getList(page.currentPage, page.pageSize, query); const data = response.data.data; if (!data) { throw new Error('响应数据格式错误'); } this.page.total = data.total || 0; /** @type {Array} */ this.data = Array.isArray(data.records) ? data.records : []; } catch (error) { console.error('加载数据失败:', error); this.$message.error(`加载数据失败: ${error.message || '未知错误'}`); // 设置默认值避免页面异常 this.page.total = 0; /** @type {Array} */ this.data = []; } finally { this.loading = false; } }, /** * 远程搜索客户数据 * @async * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @param {string} query - 搜索关键词 * @returns {Promise} * @throws {Error} 当搜索客户失败时抛出错误 */ async remoteSearchCustomers(query) { if (!query || typeof query !== 'string' || query.trim().length < 1) { /** @type {Array} */ this.customerBlacklistOptions = []; return; } this.customerOptionsLoading = true; try { const response = await getCustomerList(1, 50, { customerName: query.trim() }); if (response.data && response.data.success && response.data.data) { const customers = response.data.data.records || []; this.currentCustomerBlacklist = customers.map(c => ({ id: c.Customer_ID, Customer_NAME: c.Customer_NAME, Customer_CODE: c.Customer_CODE, Customer_ShortName: c.Customer_ShortName })) this.customerBlacklistOptions = customers.map(customer => ({ id: customer.Customer_ID, Customer_NAME: customer.Customer_NAME, Customer_CODE: customer.Customer_CODE, Customer_ShortName: customer.Customer_ShortName })); } else { /** @type {Array} */ this.customerBlacklistOptions = []; } } catch (error) { console.error('获取客户列表失败:', error); this.$message.error('获取客户列表失败,请稍后重试'); /** @type {Array} */ this.customerBlacklistOptions = []; } finally { this.customerOptionsLoading = false; } }, /** * 客户黑名单选择变化处理 * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @param {Array} selectedCustomers - 选中的客户列表 */ handleCustomerBlacklistChange(selectedCustomers) { if (!Array.isArray(selectedCustomers)) { /** @type {Array} */ this.form.customerBlacklist = []; return; } /** @type {Array} */ this.form.customerBlacklist = selectedCustomers; }, /** * 获取客户显示名称 * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} * @param {CustomerBlacklistOption} customer - 客户对象 * @returns {string} 显示名称 */ getCustomerDisplayName(customer) { if (!customer || typeof customer !== 'object') { return ''; } const name = customer.Customer_NAME || ''; const code = customer.Customer_CODE || ''; return code ? `${name}(${code})` : name; }, /** * 清空客户搜索结果 * @this {import('@/views/announcement/types').AnnouncementComponent & import('vue').default} */ clearCustomerOptions() { this.customerBlacklistOptions = []; }, /** * 处理表单组件返回事件 */ handleFormBack() { this.announcementFormVisible = false; this.isEditMode = false; this.editAnnouncementId = null; }, /** * 处理表单保存成功事件 */ handleFormSaveSuccess() { this.announcementFormVisible = false; this.isEditMode = false; this.editAnnouncementId = null; // 刷新分类字典 this.loadCategoryOptions(); // 刷新列表数据 this.onLoad(this.page); } } };