Browse Source

refactor(公告模块): 增强类型安全性和错误处理

yz 1 month ago
parent
commit
ac9f2f7b98

+ 121 - 33
src/api/announcement/category.js

@@ -2,59 +2,102 @@
  * 公告分类管理API
  * @description 提供公告分类的增删改查功能
  * @version 1.0.0
+ * @author System
  */
 import request from '@/router/axios';
 
 /**
+ * 分类状态枚举
+ * @typedef {0|1} CategoryStatus
+ * - 0: 禁用
+ * - 1: 启用
+ */
+
+/**
+ * 删除状态枚举
+ * @typedef {0|1} DeleteStatus
+ * - 0: 未删除
+ * - 1: 已删除
+ */
+
+/**
+ * 系统分类标识
+ * @typedef {0|1} SystemFlag
+ * - 0: 非系统分类
+ * - 1: 系统分类
+ */
+
+/**
  * 分类数据类型定义
  * @typedef {Object} CategoryItem
- * @property {number} id - 分类ID
- * @property {string} name - 分类名称
- * @property {number} sortOrder - 排序
- * @property {number} orgId - 组织ID
- * @property {string} orgCode - 组织编码
- * @property {string} orgName - 组织名称
- * @property {number} isSystem - 是否系统分类 (0-否, 1-是)
- * @property {string} remark - 备注
+ * @property {number} id - 分类ID(唯一标识)
+ * @property {string} name - 分类名称(必填,最大长度50)
+ * @property {number} sortOrder - 排序(数值越小越靠前,默认0)
+ * @property {number} orgId - 组织ID(必填)
+ * @property {string} orgCode - 组织编码(必填)
+ * @property {string} orgName - 组织名称(只读)
+ * @property {SystemFlag} isSystem - 是否系统分类(0-否 1-是)
+ * @property {string} remark - 备注(可选,最大长度200)
  * @property {number} createUser - 创建用户ID
  * @property {number} createDept - 创建部门ID
- * @property {string|null} createTime - 创建时间
+ * @property {string|null} createTime - 创建时间(ISO格式)
  * @property {number|null} updateUser - 更新用户ID
- * @property {string|null} updateTime - 更新时间
- * @property {number} status - 状态 (0-禁用, 1-启用)
- * @property {number} isDeleted - 是否删除 (0-否, 1-是)
+ * @property {string|null} updateTime - 更新时间(ISO格式)
+ * @property {CategoryStatus} status - 状态(0-禁用 1-启用)
+ * @property {DeleteStatus} isDeleted - 是否删除(0-否 1-是)
  */
 
 /**
  * 新增分类请求参数类型
  * @typedef {Object} AddCategoryParams
- * @property {number} createDept - 创建部门ID
- * @property {number} createUser - 创建用户ID
- * @property {string} name - 分类名称
- * @property {string} orgCode - 组织编码
- * @property {number} orgId - 组织ID
- * @property {string} orgName - 组织名称
- * @property {string} [remark] - 备注
- * @property {number} [sortOrder] - 排序
+ * @property {number} createDept - 创建部门ID(必填)
+ * @property {number} createUser - 创建用户ID(必填)
+ * @property {string} name - 分类名称(必填,最大长度50)
+ * @property {string} orgCode - 组织编码(必填)
+ * @property {number} orgId - 组织ID(必填)
+ * @property {string} orgName - 组织名称(必填)
+ * @property {string} [remark=''] - 备注(可选,最大长度200)
+ * @property {number} [sortOrder=0] - 排序(可选,默认0)
  */
 
 /**
  * 更新分类请求参数类型
- * @typedef {AddCategoryParams & {id: number}} UpdateCategoryParams
+ * @typedef {Object} UpdateCategoryParams
+ * @property {number} id - 分类ID(必填)
+ * @property {string} name - 分类名称(必填,最大长度50)
+ * @property {string} [remark] - 备注(可选,最大长度200)
+ * @property {number} [sortOrder] - 排序(可选)
+ * @property {number} [createDept] - 创建部门ID
+ * @property {number} [createUser] - 创建用户ID
+ * @property {string} [orgCode] - 组织编码
+ * @property {number} [orgId] - 组织ID
+ * @property {string} [orgName] - 组织名称
  */
 
 /**
- * API响应类型
+ * 通用API响应类型
+ * @template T
  * @typedef {Object} ApiResponse
- * @property {number} code - 响应码
+ * @property {number} code - 响应状态
  * @property {boolean} success - 是否成功
- * @property {*} data - 响应数据
  * @property {string} msg - 响应消息
+ * @property {T} data - 响应数据
+ */
+
+/**
+ * @template T
+ * @typedef {ApiResponse<T>} ApiResponseGeneric
  */
 
 /**
  * 获取分类列表
- * @returns {Promise<AxiosResponse<Array<CategoryItem>>>} 分类列表响应
+ * @returns {Promise<import('axios').AxiosResponse<ApiResponseGeneric<CategoryItem[]>>>} 分类列表响应
+ * @throws {Error} 当请求失败时抛出错误
+ * @example
+ * // 获取所有分类
+ * const response = await getCategoryList();
+ * const categories = response.data.data;
+ * console.log(categories.length);
  */
 export const getCategoryList = () => {
   return request({
@@ -74,7 +117,20 @@ export const getCategoryList = () => {
  * @param {string} params.orgName - 组织名称
  * @param {string} [params.remark=''] - 备注
  * @param {number} [params.sortOrder=0] - 排序
- * @returns {Promise<AxiosResponse<null>>} 操作结果
+ * @returns {Promise<import('axios').AxiosResponse<ApiResponseGeneric<null>>>} 操作结果
+ * @throws {Error} 当请求失败或参数验证失败时抛出错误
+ * @example
+ * // 新增分类
+ * const params = {
+ *   name: '重要公告',
+ *   createDept: 1,
+ *   createUser: 1,
+ *   orgCode: 'ORG001',
+ *   orgId: 1,
+ *   orgName: '总部',
+ *   remark: '重要公告分类'
+ * };
+ * await addCategory(params);
  */
 export const addCategory = (params) => {
   return request({
@@ -90,11 +146,23 @@ export const addCategory = (params) => {
 /**
  * 更新分类
  * @param {UpdateCategoryParams} params - 分类信息(包含id)
+ * 修改分类
+ * @param {Object} params - 分类参数
  * @param {number} params.id - 分类ID
  * @param {string} params.name - 分类名称
  * @param {string} [params.remark] - 备注
  * @param {number} [params.sortOrder] - 排序
- * @returns {Promise<AxiosResponse<null>>} 操作结果
+ * @returns {Promise<import('axios').AxiosResponse<ApiResponse<null>>>} 操作结果
+ * @throws {Error} 当请求失败、参数验证失败或分类不存在时抛出错误
+ * @example
+ * // 更新分类
+ * const params = {
+ *   id: 1,
+ *   name: '更新后的分类名称',
+ *   remark: '更新后的备注',
+ *   sortOrder: 10
+ * };
+ * await updateCategory(params);
  */
 export const updateCategory = (params) => {
   return request({
@@ -107,7 +175,14 @@ export const updateCategory = (params) => {
 /**
  * 删除分类
  * @param {string|number} ids - 分类ID,多个用逗号分隔
- * @returns {Promise<AxiosResponse<null>>} 操作结果
+ * @returns {Promise<import('axios').AxiosResponse<ApiResponse<null>>>} 操作结果
+ * @throws {Error} 当请求失败或分类不存在时抛出错误
+ * @example
+ * // 删除单个分类
+ * await removeCategory(1);
+ * 
+ * // 删除多个分类
+ * await removeCategory('1,2,3');
  */
 export const removeCategory = (ids) => {
   return request({
@@ -121,8 +196,14 @@ export const removeCategory = (ids) => {
 
 /**
  * 获取分类详情
- * @param {number} id - 分类ID
- * @returns {Promise<ApiResponse<CategoryItem>>} 分类详情
+ * @param {number} id - 分类ID(必填)
+ * @returns {Promise<import('axios').AxiosResponse<ApiResponseGeneric<CategoryItem>>>} 分类详情
+ * @throws {Error} 当请求失败或分类不存在时抛出错误
+ * @example
+ * // 获取分类详情
+ * const response = await getCategoryDetail(1);
+ * const category = response.data.data;
+ * console.log(category.name);
  */
 export const getCategoryDetail = (id) => {
   return request({
@@ -136,9 +217,16 @@ export const getCategoryDetail = (id) => {
 
 /**
  * 更新分类状态
- * @param {number} id - 分类ID
- * @param {number} status - 状态 (0-禁用, 1-启用)
- * @returns {Promise<ApiResponse<null>>} 操作结果
+ * @param {number} id - 分类ID(必填)
+ * @param {CategoryStatus} status - 状态(0-禁用 1-启用)
+ * @returns {Promise<import('axios').AxiosResponse<ApiResponse<null>>>} 操作结果
+ * @throws {Error} 当请求失败或分类不存在时抛出错误
+ * @example
+ * // 启用分类
+ * await updateCategoryStatus(1, 1);
+ * 
+ * // 禁用分类
+ * await updateCategoryStatus(1, 0);
  */
 export const updateCategoryStatus = (id, status) => {
   return request({

+ 155 - 31
src/api/announcement/index.js

@@ -1,9 +1,39 @@
 /**
  * 公告管理API
  * @description 提供公告的增删改查功能
- * @version 2.0.0
  */
 import request from '@/router/axios';
+import { ANNOUNCEMENT_STATUS, ROLE_TYPES } from '@/views/announcement/constants';
+
+/**
+ * 角色类型枚举
+ * @typedef {typeof ROLE_TYPES[keyof typeof ROLE_TYPES]} RoleType
+ */
+
+/**
+ * 公告状态枚举
+ * @typedef {typeof ANNOUNCEMENT_STATUS[keyof typeof ANNOUNCEMENT_STATUS]} AnnouncementStatus
+ */
+
+/**
+ * 可见角色掩码类型 (位运算)
+ * @typedef {number} VisibleRolesMask
+ */
+
+/**
+ * 客户黑名单项类型
+ * @typedef {Object} CustomerBlacklistItem
+ * @property {string} id - 客户ID
+ * @property {string} Customer_NAME - 客户名称
+ * @property {string} Customer_CODE - 客户编码
+ */
+
+/**
+ * 品牌范围类型
+ * @typedef {Object} BrandScope
+ * @property {string[]} brandIds - 品牌ID数组
+ * @property {string[]} brandNames - 品牌名称数组
+ */
 
 /**
  * 公告查询参数类型定义
@@ -11,25 +41,28 @@ import request from '@/router/axios';
  * @property {number} current - 当前页码
  * @property {number} size - 每页大小
  * @property {string} [categoryId] - 分类ID(可选)
- * @property {string} [title] - 公告标题(搜索用)
- * @property {string} [content] - 公告内容(搜索用)
+ * @property {string} [title] - 公告标题(模糊搜索)
+ * @property {string} [content] - 公告内容(模糊搜索)
+ * @property {AnnouncementStatus} [status] - 公告状态(可选)
+ * @property {string} [orgCode] - 组织编码(可选)
  */
 
 /**
  * 公告表单数据类型定义
  * @typedef {Object} NoticeFormData
  * @property {string} [id] - 公告ID(更新时必填)
- * @property {string} title - 公告标题
- * @property {string} content - 公告内容
- * @property {string} categoryId - 分类ID
- * @property {string} categoryName - 分类名称
- * @property {number} orgId - 组织ID
- * @property {string} orgCode - 组织编码
- * @property {string} orgName - 组织名称
- * @property {string} visibleRoles - 可见角色
- * @property {Object} [brandScope] - 品牌范围
- * @property {Object} [customerBlacklist] - 客户黑名单
- * @property {string} [remark] - 备注
+ * @property {string} title - 公告标题(必填,最大长度200)
+ * @property {string} content - 公告内容(必填,富文本格式)
+ * @property {string} categoryId - 分类ID(必填)
+ * @property {string} categoryName - 分类名称(只读)
+ * @property {number} orgId - 组织ID(必填)
+ * @property {string} orgCode - 组织编码(必填)
+ * @property {string} orgName - 组织名称(只读)
+ * @property {VisibleRolesMask} visibleRoles - 可见角色掩码(位运算)
+ * @property {BrandScope} [brandScope] - 品牌范围(可选)
+ * @property {CustomerBlacklistItem[]} [customerBlacklist] - 客户黑名单(可选)
+ * @property {string} [remark] - 备注(可选,最大长度500)
+ * @property {AnnouncementStatus} [status] - 公告状态(可选,默认为草稿)
  */
 
 /**
@@ -38,32 +71,63 @@ import request from '@/router/axios';
  * @property {string} id - 公告ID
  * @property {string} createUser - 创建用户ID
  * @property {string} createDept - 创建部门ID
- * @property {string} createTime - 创建时间
+ * @property {string} createTime - 创建时间(ISO格式)
  * @property {string} updateUser - 更新用户ID
- * @property {string} updateTime - 更新时间
- * @property {number} status - 状态 1-正常 0-禁用
- * @property {number} isDeleted - 是否删除 0-未删除 1-已删除
+ * @property {string} updateTime - 更新时间(ISO格式)
+ * @property {AnnouncementStatus} status - 公告状态(0-草稿 1-已发布 2-已下架)
+ * @property {0|1} isDeleted - 是否删除(0-未删除 1-已删除)
  * @property {string} title - 公告标题
- * @property {string} content - 公告内容
+ * @property {string} content - 公告内容(富文本HTML)
  * @property {string} categoryId - 分类ID
  * @property {string} categoryName - 分类名称
  * @property {number} orgId - 组织ID
  * @property {string} orgCode - 组织编码
  * @property {string} orgName - 组织名称
- * @property {string} visibleRoles - 可见角色
- * @property {Object|null} brandScope - 品牌范围
- * @property {Object|null} customerBlacklist - 客户黑名单
+ * @property {VisibleRolesMask} visibleRoles - 可见角色掩码(位运算)
+ * @property {BrandScope|null} brandScope - 品牌范围
+ * @property {CustomerBlacklistItem[]|null} customerBlacklist - 客户黑名单
  * @property {string} remark - 备注
  */
 
 /**
+ * 通用API响应类型
+ * @template T
+ * @typedef {Object} ApiResponse
+ * @property {number} code - 响应状态码
+ * @property {boolean} success - 是否成功
+ * @property {string} msg - 响应消息
+ * @property {T} data - 响应数据
+ */
+
+/**
+ * @template T
+ * @typedef {ApiResponse<T>} ApiResponseGeneric
+ */
+
+/**
+ * 分页响应数据结构
+ * @template T
+ * @typedef {Object} PageResponse
+ * @property {T[]} records - 记录列表
+ * @property {number} total - 总记录数
+ * @property {number} size - 每页大小
+ * @property {number} current - 当前页码
+ * @property {number} pages - 总页数
+ */
+
+/**
  * 获取公告列表
- * @param {number} current - 当前页码
- * @param {number} size - 每页大小
+ * @param {number} current - 当前页码(必须大于0)
+ * @param {number} size - 每页大小(建议10-100之间)
  * @param {NoticeQueryParams} [params={}] - 查询参数
- * @returns {Promise<AxiosResponse<{data: {data: {records: NoticeRecord[], total: number, size: number, current: number}}}>>}
+ * @returns {Promise<AxiosResponse<PageResponse<NoticeRecord>>>} 分页公告列表
+ * @throws {Error} 当请求失败时抛出错误
+ * @example
+ * // 获取第一页公告列表
+ * const response = await getList(1, 10, { title: '重要通知' });
+ * const { records, total } = response.data.data;
  */
-export const getList = (current, size, params = {}) => {
+export const getList = (current, size, params = /** @type {Partial<NoticeQueryParams>} */ ({})) => {
   return request({
     url: '/api/blade-factory/api/factory/notice/page',
     method: 'get',
@@ -79,6 +143,16 @@ export const getList = (current, size, params = {}) => {
  * 新增公告
  * @param {NoticeFormData} row - 公告表单数据
  * @returns {Promise<AxiosResponse<boolean>>} 操作结果
+ * @throws {Error} 当请求失败或数据验证失败时抛出错误
+ * @example
+ * // 新增公告
+ * const formData = {
+ *   title: '重要通知',
+ *   content: '<p>通知内容</p>',
+ *   categoryId: '1',
+ *   visibleRoles: 7 // 所有角色可见
+ * };
+ * await add(formData);
  */
 export const add = (row) => {
   return request({
@@ -90,8 +164,17 @@ export const add = (row) => {
 
 /**
  * 修改公告
- * @param {NoticeFormData} row - 公告表单数据
+ * @param {NoticeFormData} row - 公告表单数据(必须包含id)
  * @returns {Promise<AxiosResponse<boolean>>} 操作结果
+ * @throws {Error} 当请求失败、数据验证失败或公告不存在时抛出错误
+ * @example
+ * // 更新公告
+ * const formData = {
+ *   id: '123',
+ *   title: '更新后的标题',
+ *   content: '<p>更新后的内容</p>'
+ * };
+ * await update(formData);
  */
 export const update = (row) => {
   return request({
@@ -103,8 +186,14 @@ export const update = (row) => {
 
 /**
  * 获取公告详情
- * @param {string} id - 公告ID
+ * @param {string} id - 公告ID(必填)
  * @returns {Promise<AxiosResponse<NoticeRecord>>} 公告详情
+ * @throws {Error} 当请求失败或公告不存在时抛出错误
+ * @example
+ * // 获取公告详情
+ * const response = await getAnnouncement('123');
+ * const notice = response.data.data;
+ * console.log(notice.title);
  */
 export const getAnnouncement = (id) => {
   return request({
@@ -117,8 +206,18 @@ export const getAnnouncement = (id) => {
 };
 
 /**
+ * 经销商选项类型
+ * @typedef {Object} DealerOption
+ * @property {string} id - 经销商ID
+ * @property {string} name - 经销商名称
+ * @property {string} [code] - 经销商编码
+ */
+
+/**
  * 获取经销商列表(保留兼容性)
- * @returns {Promise<AxiosResponse<Array<{id: string, name: string}>>>}
+ * @returns {Promise<import('axios').AxiosResponse<ApiResponseGeneric<DealerOption[]>>>} 经销商列表
+ * @throws {Error} 当请求失败时抛出错误
+ * @deprecated 建议使用更具体的经销商API
  */
 export const getDealerList = () => {
   return request({
@@ -128,8 +227,18 @@ export const getDealerList = () => {
 };
 
 /**
+ * 品牌选项类型
+ * @typedef {Object} BrandOption
+ * @property {string} id - 品牌ID
+ * @property {string} name - 品牌名称
+ * @property {string} [code] - 品牌编码
+ */
+
+/**
  * 获取品牌列表(保留兼容性)
- * @returns {Promise<AxiosResponse<Array<{id: string, name: string}>>>}
+ * @returns {Promise<AxiosResponse<BrandOption[]>>} 品牌列表
+ * @throws {Error} 当请求失败时抛出错误
+ * @deprecated 建议使用更具体的品牌API
  */
 export const getBrandList = () => {
   return request({
@@ -139,8 +248,23 @@ export const getBrandList = () => {
 };
 
 /**
+ * 分类选项类型
+ * @typedef {Object} CategoryOption
+ * @property {string} id - 分类ID
+ * @property {string} name - 分类名称
+ * @property {number} sortOrder - 排序
+ * @property {string} orgCode - 组织编码
+ * @property {number} status - 状态(0-禁用 1-启用)
+ */
+
+/**
  * 获取分类列表
- * @returns {Promise<any>}
+ * @returns {Promise<AxiosResponse<CategoryOption[]>>} 分类列表
+ * @throws {Error} 当请求失败时抛出错误
+ * @example
+ * // 获取分类列表
+ * const response = await getCategoryList();
+ * const categories = response.data.data;
  */
 export const getCategoryList = () => {
   return request({

+ 82 - 9
src/views/announcement/constants.js

@@ -130,8 +130,13 @@ export const STATUS_OPTIONS = [
  * 获取角色标签文本
  * @param {RoleType} roleValue - 角色值
  * @returns {string} 角色标签文本
+ * @throws {TypeError} 当参数类型不正确时抛出错误
  */
 export const getRoleLabel = (roleValue) => {
+    if (typeof roleValue !== 'number') {
+        console.warn('getRoleLabel: roleValue should be a number, received:', typeof roleValue);
+        return '未知角色';
+    }
     return ROLE_LABELS[roleValue] || '未知角色';
 };
 
@@ -139,8 +144,13 @@ export const getRoleLabel = (roleValue) => {
  * 获取角色标签类型
  * @param {RoleType} roleValue - 角色值
  * @returns {string} Element UI标签类型
+ * @throws {TypeError} 当参数类型不正确时抛出错误
  */
 export const getRoleTagType = (roleValue) => {
+    if (typeof roleValue !== 'number') {
+        console.warn('getRoleTagType: roleValue should be a number, received:', typeof roleValue);
+        return 'default';
+    }
     return ROLE_TAG_TYPES[roleValue] || 'default';
 };
 
@@ -148,8 +158,13 @@ export const getRoleTagType = (roleValue) => {
  * 获取状态标签文本
  * @param {AnnouncementStatus} statusValue - 状态值
  * @returns {string} 状态标签文本
+ * @throws {TypeError} 当参数类型不正确时抛出错误
  */
 export const getStatusLabel = (statusValue) => {
+    if (typeof statusValue !== 'number') {
+        console.warn('getStatusLabel: statusValue should be a number, received:', typeof statusValue);
+        return '未知状态';
+    }
     return STATUS_LABELS[statusValue] || '未知状态';
 };
 
@@ -157,8 +172,13 @@ export const getStatusLabel = (statusValue) => {
  * 获取状态标签类型
  * @param {AnnouncementStatus} statusValue - 状态值
  * @returns {string} Element UI标签类型
+ * @throws {TypeError} 当参数类型不正确时抛出错误
  */
 export const getStatusTagType = (statusValue) => {
+    if (typeof statusValue !== 'number') {
+        console.warn('getStatusTagType: statusValue should be a number, received:', typeof statusValue);
+        return 'default';
+    }
     return STATUS_TAG_TYPES[statusValue] || 'default';
 };
 
@@ -166,32 +186,73 @@ export const getStatusTagType = (statusValue) => {
  * 计算角色掩码值 (位运算)
  * @param {RoleType[]} selectedRoles - 选中的角色数组
  * @returns {VisibleRolesMask} 角色掩码值
+ * @throws {TypeError} 当参数不是数组或包含无效角色值时抛出错误
  */
 export const calculateRolesMask = (selectedRoles) => {
-    if (!Array.isArray(selectedRoles) || selectedRoles.length === 0) {
+    if (!Array.isArray(selectedRoles)) {
+        console.warn('calculateRolesMask: selectedRoles should be an array, received:', typeof selectedRoles);
         return 0;
     }
-    return selectedRoles.reduce((mask, role) => mask | role, 0);
+    
+    if (selectedRoles.length === 0) {
+        return 0;
+    }
+    
+    // 验证所有角色值都是有效的数字
+    const validRoles = selectedRoles.filter(role => {
+        if (typeof role !== 'number' || !Object.values(ROLE_TYPES).includes(role)) {
+            console.warn('calculateRolesMask: invalid role value:', role);
+            return false;
+        }
+        return true;
+    });
+    
+    return validRoles.reduce((mask, role) => mask | role, 0);
 };
 
 /**
  * 解析角色掩码为角色数组
  * @param {VisibleRolesMask|string} rolesMask - 角色掩码值
  * @returns {RoleOption[]} 角色选项数组
+ * @throws {TypeError} 当参数类型无法转换为有效数字时抛出错误
  */
 export const parseRolesMask = (rolesMask) => {
     // 处理空值
-    if (!rolesMask && rolesMask !== 0) {
+    if (rolesMask === null || rolesMask === undefined) {
+        return [];
+    }
+    
+    // 处理0值(有效的掩码值)
+    if (rolesMask === 0) {
         return [];
     }
 
     // 转换为数字类型
-    const numericMask = typeof rolesMask === 'string' ? parseInt(rolesMask, 10) : rolesMask;
+    let numericMask;
+    if (typeof rolesMask === 'string') {
+        numericMask = parseInt(rolesMask, 10);
+        if (isNaN(numericMask)) {
+            console.warn('parseRolesMask: unable to parse string to number:', rolesMask);
+            return [];
+        }
+    } else if (typeof rolesMask === 'number') {
+        numericMask = rolesMask;
+    } else {
+        console.warn('parseRolesMask: invalid rolesMask type:', typeof rolesMask);
+        return [];
+    }
 
     // 验证转换后的数字是否有效
-    if (typeof numericMask !== 'number' || isNaN(numericMask) || numericMask < 0) {
+    if (numericMask < 0) {
+        console.warn('parseRolesMask: rolesMask cannot be negative:', numericMask);
         return [];
     }
+    
+    // 验证掩码值是否在有效范围内(1-7)
+    const maxMask = Object.values(ROLE_TYPES).reduce((sum, role) => sum | role, 0);
+    if (numericMask > maxMask) {
+        console.warn('parseRolesMask: rolesMask exceeds maximum valid value:', numericMask, 'max:', maxMask);
+    }
 
     return ROLE_OPTIONS.filter(role => (numericMask & role.value) === role.value);
 };
@@ -200,18 +261,30 @@ export const parseRolesMask = (rolesMask) => {
  * 获取可见角色文本数组
  * @param {VisibleRolesMask} visibleRoles - 可见角色掩码
  * @returns {string[]} 角色文本数组
+ * @throws {TypeError} 当参数无法解析为有效角色时抛出错误
  */
 export const getVisibleRolesTextArray = (visibleRoles) => {
-    const roles = parseRolesMask(visibleRoles);
-    return roles.map(role => role.label);
+    try {
+        const roles = parseRolesMask(visibleRoles);
+        return Array.isArray(roles) ? roles.map(role => role.label || '未知角色') : [];
+    } catch (error) {
+        console.warn('getVisibleRolesTextArray: error parsing roles:', error);
+        return [];
+    }
 };
 
 /**
  * 获取可见角色文本 (逗号分隔)
  * @param {VisibleRolesMask} visibleRoles - 可见角色掩码
  * @returns {string} 角色文本字符串
+ * @throws {TypeError} 当参数无法解析为有效角色时抛出错误
  */
 export const getVisibleRolesText = (visibleRoles) => {
-    const textArray = getVisibleRolesTextArray(visibleRoles);
-    return textArray.length > 0 ? textArray.join('、') : '无';
+    try {
+        const textArray = getVisibleRolesTextArray(visibleRoles);
+        return Array.isArray(textArray) && textArray.length > 0 ? textArray.join('、') : '无';
+    } catch (error) {
+        console.warn('getVisibleRolesText: error getting roles text:', error);
+        return '无';
+    }
 };

+ 184 - 125
src/views/announcement/mixins/announcementIndex.js

@@ -1,9 +1,11 @@
 import { getList, update, add, getAnnouncement, getCategoryList } from "@/api/announcement";
-import { getCustomerList } from "@/api/common/customer"; // 新增客户接口导入
+import { getCustomerList } from "@/api/common/customer";
 import { mapGetters } from "vuex";
 import {
     ROLE_OPTIONS,
     STATUS_OPTIONS,
+    ANNOUNCEMENT_STATUS,
+    ROLE_TYPES,
     getRoleLabel,
     getRoleTagType,
     getStatusLabel,
@@ -15,79 +17,46 @@ import {
 } from '@/views/announcement/constants';
 
 /**
- * 角色类型枚举
- * @typedef {1|2|4} RoleType
- * - 1: 工厂
- * - 2: 经销商
- * - 4: 零售商
+ * 角色类型定义
+ * @typedef {import('@/views/announcement/constants').RoleType} RoleType
  */
 
 /**
- * 可见角色掩码类型 (位运算)
- * @typedef {number} VisibleRolesMask
- * 支持的值:1(工厂) | 2(经销商) | 4(零售商) 及其组合
- * 例如:3(工厂+经销商), 5(工厂+零售商), 6(经销商+零售商), 7(全部)
+ * 可见角色掩码类型定义
+ * @typedef {import('@/views/announcement/constants').VisibleRolesMask} VisibleRolesMask
  */
 
 /**
  * 角色选项类型定义
- * @typedef {Object} RoleOption
- * @property {string} label - 角色显示名称
- * @property {RoleType} value - 角色值(位运算)
+ * @typedef {import('@/views/announcement/constants').RoleOption} RoleOption
  */
 
 /**
- * 公告状态枚举类型
- * @typedef {0|1|2} AnnouncementStatus
- * - 0: 草稿
- * - 1: 已发布
- * - 2: 已下架
+ * 公告状态类型定义
+ * @typedef {import('@/views/announcement/constants').AnnouncementStatus} AnnouncementStatus
  */
 
 /**
- * 公告数据类型定义
- * @typedef {Object} NoticeItem
- * @property {string} id - 公告ID
- * @property {string} title - 公告标题
- * @property {string} content - 公告内容
- * @property {string} categoryId - 分类ID
- * @property {string} categoryName - 分类名称
- * @property {number} orgId - 组织ID
- * @property {string} orgCode - 组织编码
- * @property {string} orgName - 组织名称
- * @property {VisibleRolesMask} visibleRoles - 可见角色掩码(位运算:1工厂 2经销商 4零售商)
- * @property {Object|null} brandScope - 品牌范围
- * @property {Array<CustomerBlacklistOption>|null} customerBlacklist - 客户黑名单
- * @property {string} remark - 备注
- * @property {string} createTime - 创建时间
- * @property {string} updateTime - 更新时间
- * @property {AnnouncementStatus} status - 状态 0草稿 1已发布 2已下架
- * @property {number} isDeleted - 是否删除
+ * 公告数据项类型定义
+ * @typedef {import('@/api/announcement').NoticeRecord} NoticeItem
  */
 
 /**
  * 分类选项类型定义
- * @typedef {Object} CategoryOption
- * @property {string} id - 分类ID
- * @property {string} name - 分类名称
- * @property {string} value - 选项值
- * @property {string} label - 选项标签
+ * @typedef {import('@/api/announcement').CategoryOption} CategoryOption
  */
 
 /**
  * 分页信息类型定义
  * @typedef {Object} PageInfo
- * @property {number} pageSize - 页大小
- * @property {number} currentPage - 当前页
+ * @property {number} pageSize - 页大小(默认10)
+ * @property {number} currentPage - 当前页(从1开始)
  * @property {number} total - 总记录数
  */
 
 /**
  * 查询参数类型定义
- * @typedef {Object} QueryParams
- * @property {string} [title] - 公告标题
- * @property {string} [categoryId] - 分类ID
- * @property {string} [content] - 公告内容
+ * @typedef {import('@/api/announcement').NoticeQueryParams} QueryParams
  */
 
 /**
@@ -142,17 +111,13 @@ import {
  * @mixin AnnouncementIndexMixin
  */
 /**
- * 客户选项类型定义
+ * 客户记录类型定义
  * @typedef {import('@/api/common/customer').CustomerRecord} CustomerRecord
  */
 
 /**
  * 客户黑名单选项类型定义
- * @typedef {Object} CustomerBlacklistOption
- * @property {string} id - 客户ID
- * @property {string} Customer_NAME - 客户名称
- * @property {string} Customer_CODE - 客户编码
- * @property {string} Customer_ShortName - 客户简称
+ * @typedef {import('@/api/announcement').CustomerBlacklistItem} CustomerBlacklistOption
  */
 
 export default {
@@ -181,16 +146,10 @@ export default {
             /** @type {Array<CategoryOption>} 分类选项列表 */
             categoryOptions: [],
             /** @type {Array<RoleOption>} 角色选项列表 */
-            roleOptions: [
-                { label: "工厂", value: 1 },
-                { label: "经销商", value: 2 },
-                { label: "零售商", value: 4 }
-            ],
+            roleOptions: ROLE_OPTIONS,
             /** @type {Array<CustomerBlacklistOption>} 客户黑名单选项列表 */
             customerBlacklistOptions: [],
-            /**
-             * @type {Array<CustomerRecord>} 当前客户黑名单
-             */
+            /** @type {Array<CustomerRecord>} 当前客户黑名单 */
             currentCustomerBlacklist: [],
             /** @type {boolean} 客户选项加载状态 */
             customerOptionsLoading: false,
@@ -423,12 +382,14 @@ export default {
          * 加载分类选项
          * @async
          * @returns {Promise<void>}
+         * @throws {Error} 当加载分类选项失败时抛出错误
          */
         async loadCategoryOptions() {
             try {
-                const res = await getCategoryList();
-                const categoryData = res.data.data || [];
+                const response = await getCategoryList();
+                const categoryData = response.data?.data || [];
 
+                /** @type {Array<CategoryOption>} */
                 this.categoryOptions = categoryData
                     .filter(item => item.status === 1 && item.isDeleted === 0)
                     .map(item => ({
@@ -442,17 +403,22 @@ export default {
                     }))
                     .sort((a, b) => a.sortOrder - b.sortOrder);
 
+                // 更新表格列配置中的字典数据
                 const categoryColumn = this.option.column.find(col => col.prop === 'categoryId');
                 if (categoryColumn) {
                     categoryColumn.dicData = this.categoryOptions;
                 }
             } catch (error) {
                 console.error('加载分类选项失败:', error);
-                // 使用默认分类
+                this.$message.error('加载分类选项失败,使用默认分类');
+                
+                // 使用默认分类选项
+                /** @type {Array<CategoryOption>} */
                 this.categoryOptions = [
-                    { id: '1', name: '系统公告', value: '1', label: '系统公告' },
-                    { id: '2', name: '部门公告', value: '2', label: '部门公告' }
+                    { id: 1, name: '系统公告', value: 1, label: '系统公告', sortOrder: 0 },
+                    { id: 2, name: '部门公告', value: 2, label: '部门公告', sortOrder: 1 }
                 ];
+                
                 const categoryColumn = this.option.column.find(col => col.prop === 'categoryId');
                 if (categoryColumn) {
                     categoryColumn.dicData = this.categoryOptions;
@@ -464,19 +430,22 @@ export default {
          * 查看详情
          * @async
          * @returns {Promise<void>}
+         * @throws {Error} 当获取详情失败时抛出错误
          */
         async handleDetail() {
             if (this.selectionList.length !== 1) {
                 this.$message.warning("请选择一条数据查看详情");
                 return;
             }
+            
             try {
-                const res = await getAnnouncement(this.selectionList[0].id);
-                this.currentDetail = res.data.data;
+                const response = await getAnnouncement(this.selectionList[0].id);
+                /** @type {NoticeItem} */
+                this.currentDetail = response.data?.data || {};
                 this.detailVisible = true;
             } catch (error) {
                 console.error('获取详情失败:', error);
-                this.$message.error('获取详情失败');
+                this.$message.error('获取详情失败,请稍后重试');
             }
         },
 
@@ -487,43 +456,56 @@ export default {
          * @param {Function} done - 完成回调
          * @param {Function} loading - 加载回调
          * @returns {Promise<void>}
+         * @throws {Error} 当保存失败时抛出错误
          */
         async rowSave(row, done, loading) {
             try {
+                // 参数验证
+                if (!row.title?.trim()) {
+                    this.$message.error('公告标题不能为空');
+                    loading();
+                    return;
+                }
+                
+                if (!row.content?.trim()) {
+                    this.$message.error('公告内容不能为空');
+                    loading();
+                    return;
+                }
+
                 // 计算角色掩码值
+                /** @type {VisibleRolesMask} */
                 const rolesMask = Array.isArray(row.visibleRoles)
                     ? this.calculateRolesMask(row.visibleRoles)
                     : (row.visibleRoles || 7); // 默认所有角色可见
 
-                /**
-                 * 过滤后的客户黑名单数据
-                 * @type {Array<CustomerRecord>}
-                 */
-                const updateCustomerBlacklist = this.currentCustomerBlacklist.filter(customer => {
-                    const ids = row.customerBlacklist.map(customer => customer.id);
-                    return ids.includes(customer.id);
+                // 处理客户黑名单数据
+                /** @type {Array<CustomerRecord>} */
+                const filteredCustomerBlacklist = this.currentCustomerBlacklist.filter(customer => {
+                    const selectedIds = (row.customerBlacklist || []).map(item => item.id);
+                    return selectedIds.includes(customer.id);
                 });
 
-                // 设置默认值
+                // 构建表单数据
+                /** @type {import('@/api/announcement').NoticeFormData} */
                 const formData = {
                     ...row,
-                    orgId: this.userInfo.orgId || row.orgId,
-                    orgCode: this.userInfo.orgCode || row.orgCode,
-                    orgName: this.userInfo.orgName || row.orgName,
+                    orgId: this.userInfo?.orgId || row.orgId,
+                    orgCode: this.userInfo?.orgCode || row.orgCode,
+                    orgName: this.userInfo?.orgName || row.orgName,
                     brandScope: row.brandScope || {},
-                    // customerBlacklist: Array.isArray(row.customerBlacklist) ? row.customerBlacklist : [],
-                    customerBlacklist: updateCustomerBlacklist.map(customer => ({
+                    customerBlacklist: filteredCustomerBlacklist.map(customer => ({
                         ID: customer.id,
                         CODE: customer.Customer_CODE,
                         NAME: customer.Customer_NAME
                     })),
                     remark: row.remark || '',
                     visibleRoles: rolesMask,
-                    status: row.status !== undefined ? row.status : 0, // 默认草稿状态
+                    status: row.status !== undefined ? row.status : ANNOUNCEMENT_STATUS.DRAFT,
                 };
 
                 // 设置分类名称
-                const selectedCategory = this.categoryOptions.find(cat => cat.id === row.categoryId);
+                const selectedCategory = this.categoryOptions.find(cat => cat.id == row.categoryId);
                 if (selectedCategory) {
                     formData.categoryName = selectedCategory.name;
                 }
@@ -532,12 +514,12 @@ export default {
                 await this.onLoad(this.page);
                 this.$message({
                     type: "success",
-                    message: "操作成功!"
+                    message: "新增公告成功!"
                 });
                 done();
             } catch (error) {
                 console.error('保存失败:', error);
-                this.$message.error('保存失败');
+                this.$message.error(`保存失败: ${error.message || '未知错误'}`);
                 loading();
             }
         },
@@ -550,34 +532,54 @@ export default {
          * @param {Function} done - 完成回调
          * @param {Function} loading - 加载回调
          * @returns {Promise<void>}
+         * @throws {Error} 当更新失败时抛出错误
          */
         async rowUpdate(row, index, done, loading) {
             try {
+                // 参数验证
+                if (!row.id) {
+                    this.$message.error('缺少公告ID,无法更新');
+                    loading();
+                    return;
+                }
+                
+                if (!row.title?.trim()) {
+                    this.$message.error('公告标题不能为空');
+                    loading();
+                    return;
+                }
+                
+                if (!row.content?.trim()) {
+                    this.$message.error('公告内容不能为空');
+                    loading();
+                    return;
+                }
+
                 // 计算角色掩码值
+                /** @type {VisibleRolesMask} */
                 const rolesMask = Array.isArray(row.visibleRoles)
                     ? this.calculateRolesMask(row.visibleRoles)
                     : row.visibleRoles;
 
                 // 设置分类名称
-                const selectedCategory = this.categoryOptions.find(cat => cat.id === row.categoryId);
+                const selectedCategory = this.categoryOptions.find(cat => cat.id == row.categoryId);
                 if (selectedCategory) {
                     row.categoryName = selectedCategory.name;
                 }
 
-                /**
-                 * 过滤后的客户黑名单数据
-                 * @type {Array<CustomerRecord>}
-                 */
-                const updateCustomerBlacklist = this.currentCustomerBlacklist.filter(customer => {
-                    const ids = row.customerBlacklist.map(customer => customer.id);
-                    return ids.includes(customer.id);
+                // 处理客户黑名单数据
+                /** @type {Array<CustomerRecord>} */
+                const filteredCustomerBlacklist = this.currentCustomerBlacklist.filter(customer => {
+                    const selectedIds = (row.customerBlacklist || []).map(item => item.id);
+                    return selectedIds.includes(customer.id);
                 });
-                // 确保必要字段存在
+                
+                // 构建更新数据
+                /** @type {import('@/api/announcement').NoticeFormData} */
                 const formData = {
                     ...row,
                     brandScope: row.brandScope || {},
-                    // customerBlacklist: Array.isArray(row.customerBlacklist) ? row.customerBlacklist : [],
-                    customerBlacklist: updateCustomerBlacklist.map(customer => ({
+                    customerBlacklist: filteredCustomerBlacklist.map(customer => ({
                         ID: customer.id,
                         CODE: customer.Customer_CODE,
                         NAME: customer.Customer_NAME
@@ -590,12 +592,12 @@ export default {
                 await this.onLoad(this.page);
                 this.$message({
                     type: "success",
-                    message: "操作成功!"
+                    message: "更新公告成功!"
                 });
                 done();
             } catch (error) {
                 console.error('更新失败:', error);
-                this.$message.error('更新失败');
+                this.$message.error(`更新失败: ${error.message || '未知错误'}`);
                 loading();
             }
         },
@@ -606,8 +608,9 @@ export default {
          * @param {Function} done - 完成回调
          */
         searchChange(params, done) {
-            this.query = params;
-            this.onLoad(this.page, params);
+            /** @type {QueryParams} */
+            this.query = params || {};
+            this.onLoad(this.page, this.query);
             done();
         },
 
@@ -615,6 +618,7 @@ export default {
          * 搜索重置事件
          */
         searchReset() {
+            /** @type {QueryParams} */
             this.query = {};
             this.onLoad(this.page);
         },
@@ -624,15 +628,18 @@ export default {
          * @param {Array<NoticeItem>} list - 选中的数据列表
          */
         selectionChange(list) {
-            this.selectionList = list;
+            /** @type {Array<NoticeItem>} */
+            this.selectionList = Array.isArray(list) ? list : [];
         },
 
         /**
          * 当前页变化事件
-         * @param {number} currentPage - 当前页码
+         * @param {number} currentPage - 当前页码(从1开始)
          */
         currentChange(currentPage) {
-            this.page.currentPage = currentPage;
+            if (typeof currentPage === 'number' && currentPage > 0) {
+                this.page.currentPage = currentPage;
+            }
         },
 
         /**
@@ -640,7 +647,9 @@ export default {
          * @param {number} pageSize - 页大小
          */
         sizeChange(pageSize) {
-            this.page.pageSize = pageSize;
+            if (typeof pageSize === 'number' && pageSize > 0) {
+                this.page.pageSize = pageSize;
+            }
         },
 
         /**
@@ -654,16 +663,25 @@ export default {
          * 打开前回调
          * @async
          * @param {Function} done - 完成回调
-         * @param {string} type - 操作类型
+         * @param {string} type - 操作类型(add/edit/view)
          * @returns {Promise<void>}
+         * @throws {Error} 当获取详情失败时抛出错误
          */
         async beforeOpen(done, type) {
             if (["edit", "view"].includes(type)) {
                 try {
-                    const res = await getAnnouncement(this.form.id);
-                    const formData = res.data.data;
+                    if (!this.form?.id) {
+                        this.$message.error('缺少公告ID,无法获取详情');
+                        done();
+                        return;
+                    }
+                    
+                    const response = await getAnnouncement(this.form.id);
+                    /** @type {NoticeItem} */
+                    const formData = response.data?.data || {};
+                    
                     // 将掩码值转换为数值数组格式供表单使用
-                    const roleObjects = this.parseRolesMask(formData.visibleRoles);
+                    const roleObjects = this.parseRolesMask(formData.visibleRoles || 0);
                     formData.visibleRoles = roleObjects.map(role => role.value);
 
                     // 确保客户黑名单是数组格式
@@ -673,25 +691,31 @@ export default {
 
                     // 如果有客户黑名单数据,设置到选项中以便显示
                     if (formData.customerBlacklist.length > 0) {
+                        /** @type {Array<CustomerBlacklistOption>} */
                         this.customerBlacklistOptions = [...formData.customerBlacklist];
                     }
 
+                    /** @type {NoticeItem} */
                     this.form = formData;
                 } catch (error) {
                     console.error('获取详情失败:', error);
+                    this.$message.error('获取详情失败,请稍后重试');
                 }
             } else if (type === "add") {
                 // 新增时设置默认值
+                /** @type {NoticeItem} */
                 this.form = {
-                    orgId: this.userInfo.orgId || '',
-                    orgCode: this.userInfo.orgCode || '',
-                    orgName: this.userInfo.orgName || '',
-                    visibleRoles: [], // 默认经销商
+                    orgId: this.userInfo?.orgId || '',
+                    orgCode: this.userInfo?.orgCode || '',
+                    orgName: this.userInfo?.orgName || '',
+                    visibleRoles: [ROLE_TYPES.DEALER], // 默认经销商
                     brandScope: {},
                     customerBlacklist: [],
-                    remark: ''
+                    remark: '',
+                    status: ANNOUNCEMENT_STATUS.DRAFT
                 };
                 // 清空客户选项
+                /** @type {Array<CustomerBlacklistOption>} */
                 this.customerBlacklistOptions = [];
             }
             done();
@@ -701,25 +725,42 @@ export default {
          * 加载数据
          * @async
          * @param {PageInfo} page - 分页信息
-         * @param {QueryParams} params - 查询参数
+         * @param {QueryParams} [params={}] - 查询参数
          * @returns {Promise<void>}
+         * @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 res = await getList(page.currentPage, page.pageSize, query);
-                const data = res.data.data;
-                this.page.total = data.total;
-                this.data = data.records;
+                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<NoticeItem>} */
+                this.data = Array.isArray(data.records) ? data.records : [];
             } catch (error) {
                 console.error('加载数据失败:', error);
-                this.$message.error('加载数据失败');
+                this.$message.error(`加载数据失败: ${error.message || '未知错误'}`);
+                // 设置默认值避免页面异常
+                this.page.total = 0;
+                /** @type {Array<NoticeItem>} */
+                this.data = [];
             } finally {
                 this.loading = false;
             }
@@ -730,9 +771,11 @@ export default {
          * @async
          * @param {string} query - 搜索关键词
          * @returns {Promise<void>}
+         * @throws {Error} 当搜索客户失败时抛出错误
          */
         async remoteSearchCustomers(query) {
-            if (!query || query.trim().length < 1) {
+            if (!query || typeof query !== 'string' || query.trim().length < 2) {
+                /** @type {Array<CustomerBlacklistOption>} */
                 this.customerBlacklistOptions = [];
                 return;
             }
@@ -750,6 +793,7 @@ export default {
                     const customers = response.data.data.records || [];
                     this.currentCustomerBlacklist = customers;
 
+                    /** @type {Array<CustomerBlacklistOption>} */
                     this.customerBlacklistOptions = customers.map(customer => ({
                         id: customer.id,
                         Customer_NAME: customer.Customer_NAME,
@@ -757,11 +801,13 @@ export default {
                         Customer_ShortName: customer.Customer_ShortName
                     }));
                 } else {
+                    /** @type {Array<CustomerBlacklistOption>} */
                     this.customerBlacklistOptions = [];
                 }
             } catch (error) {
                 console.error('获取客户列表失败:', error);
-                this.$message.error('获取客户列表失败');
+                this.$message.error('获取客户列表失败,请稍后重试');
+                /** @type {Array<CustomerBlacklistOption>} */
                 this.customerBlacklistOptions = [];
             } finally {
                 this.customerOptionsLoading = false;
@@ -773,7 +819,13 @@ export default {
          * @param {Array<CustomerBlacklistOption>} selectedCustomers - 选中的客户列表
          */
         handleCustomerBlacklistChange(selectedCustomers) {
-            this.form.customerBlacklist = selectedCustomers || [];
+            if (!Array.isArray(selectedCustomers)) {
+                /** @type {Array<CustomerBlacklistOption>} */
+                this.form.customerBlacklist = [];
+                return;
+            }
+            /** @type {Array<CustomerBlacklistOption>} */
+            this.form.customerBlacklist = selectedCustomers;
         },
 
         /**
@@ -782,15 +834,22 @@ export default {
          * @returns {string} 显示名称
          */
         getCustomerDisplayName(customer) {
-            if (!customer) return '';
-            return `${customer.Customer_NAME}(${customer.Customer_CODE})`;
+            if (!customer || typeof customer !== 'object') {
+                return '';
+            }
+            const name = customer.Customer_NAME || '';
+            const code = customer.Customer_CODE || '';
+            return code ? `${name}(${code})` : name;
         },
 
         /**
          * 清空客户搜索结果
          */
         clearCustomerOptions() {
+            /** @type {Array<CustomerBlacklistOption>} */
             this.customerBlacklistOptions = [];
+            /** @type {Array<CustomerRecord>} */
+            this.currentCustomerBlacklist = [];
         },
 
     }