|
@@ -0,0 +1,797 @@
|
|
|
+// @ts-check
|
|
|
+/**
|
|
|
+ * 公告表单混入组件
|
|
|
+ * @fileoverview 提供公告表单的核心业务逻辑,包括数据转换、表单验证、API调用等
|
|
|
+ */
|
|
|
+
|
|
|
+/**
|
|
|
+ * @typedef {import('./types').AnnouncementFormMixinComponent} AnnouncementFormMixinComponent
|
|
|
+ * @typedef {import('./types').AnnouncementFormModel} AnnouncementFormModel
|
|
|
+ * @typedef {import('./types').RoleType} RoleType
|
|
|
+ * @typedef {import('./types').CustomerOption} CustomerOption
|
|
|
+ * @typedef {import('./types').BrandOption} BrandOption
|
|
|
+ * @typedef {import('./types').CategoryOption} CategoryOption
|
|
|
+ * @typedef {import('./types').BrandScopeItem} BrandScopeItem
|
|
|
+ * @typedef {import('./types').CustomerBlacklistItem} CustomerBlacklistItem
|
|
|
+ * @typedef {import('@/api/types/announcement').NoticeRecord} NoticeRecord
|
|
|
+ * @typedef {import('@/api/types/announcement').NoticeFormData} NoticeFormData
|
|
|
+ */
|
|
|
+
|
|
|
+import {
|
|
|
+ getAnnouncement,
|
|
|
+ add as addAnnouncement,
|
|
|
+ update as updateAnnouncement
|
|
|
+} from '@/api/announcement';
|
|
|
+import { getCategoryList } from '@/api/announcement/category';
|
|
|
+import {
|
|
|
+ ROLE_OPTIONS,
|
|
|
+ STATUS_OPTIONS,
|
|
|
+ calculateRolesMask,
|
|
|
+ parseRolesMask
|
|
|
+} from '@/views/announcement/constants';
|
|
|
+
|
|
|
+
|
|
|
+/**
|
|
|
+ * @typedef {import('./types').AnnouncementFormMixinData} AnnouncementFormMixinData
|
|
|
+ * @typedef {import('./types').DataConverter} DataConverter
|
|
|
+ */
|
|
|
+
|
|
|
+/**
|
|
|
+ * 数据转换工具类
|
|
|
+ * @type {DataConverter}
|
|
|
+ */
|
|
|
+const dataConverter = {
|
|
|
+ /**
|
|
|
+ * 将API数据转换为功能模型
|
|
|
+ * @param {NoticeRecord} apiData API返回的公告数据
|
|
|
+ * @returns {AnnouncementFormModel} 功能模型数据
|
|
|
+ */
|
|
|
+ apiToFormModel(apiData) {
|
|
|
+ return {
|
|
|
+ id: apiData.id,
|
|
|
+ title: apiData.title || '',
|
|
|
+ content: apiData.content || '',
|
|
|
+ categoryId: apiData.categoryId || '',
|
|
|
+ categoryName: apiData.categoryName,
|
|
|
+ orgId: apiData.orgId || 0,
|
|
|
+ orgCode: apiData.orgCode || '',
|
|
|
+ orgName: apiData.orgName || '',
|
|
|
+ visibleRoles: this.parseVisibleRoles(apiData.visibleRoles),
|
|
|
+ brandScope: this.parseBrandScope(apiData.brandScope),
|
|
|
+ customerBlacklist: this.parseCustomerBlacklist(apiData.customerBlacklist),
|
|
|
+ remark: apiData.remark || '',
|
|
|
+ status: apiData.status || 0,
|
|
|
+ createTime: apiData.createTime,
|
|
|
+ updateTime: apiData.updateTime
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 将功能模型转换为API数据
|
|
|
+ * @param {AnnouncementFormModel} formModel 功能模型数据
|
|
|
+ * @returns {NoticeFormData} API提交数据
|
|
|
+ */
|
|
|
+ formModelToApi(formModel) {
|
|
|
+ return {
|
|
|
+ id: formModel.id,
|
|
|
+ title: formModel.title,
|
|
|
+ content: formModel.content,
|
|
|
+ categoryId: formModel.categoryId,
|
|
|
+ orgId: Number(formModel.orgId),
|
|
|
+ orgCode: formModel.orgCode,
|
|
|
+ orgName: formModel.orgName,
|
|
|
+ visibleRoles: this.calculateRolesMask(formModel.visibleRoles),
|
|
|
+ brandScope: this.stringifyBrandScope(formModel.brandScope),
|
|
|
+ customerBlacklist: this.stringifyCustomerBlacklist(formModel.customerBlacklist),
|
|
|
+ remark: formModel.remark,
|
|
|
+ status: formModel.status
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析可见角色掩码为角色数组
|
|
|
+ * @param {string | number} rolesMask 角色掩码值
|
|
|
+ * @returns {Array<RoleType>} 角色类型数组
|
|
|
+ */
|
|
|
+ parseVisibleRoles(rolesMask) {
|
|
|
+ if (!rolesMask) return [];
|
|
|
+ const mask = typeof rolesMask === 'string' ? parseInt(rolesMask, 10) : rolesMask;
|
|
|
+ return parseRolesMask(mask);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算角色数组的掩码值
|
|
|
+ * @param {Array<RoleType>} roles 角色类型数组
|
|
|
+ * @returns {number} 角色掩码值
|
|
|
+ */
|
|
|
+ calculateRolesMask(roles) {
|
|
|
+ if (!Array.isArray(roles) || roles.length === 0) return 0;
|
|
|
+ return calculateRolesMask(roles);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析品牌范围JSON字符串
|
|
|
+ * @param {string} brandScopeStr 品牌范围JSON字符串
|
|
|
+ * @returns {Array<BrandScopeItem>} 品牌范围数组
|
|
|
+ */
|
|
|
+ parseBrandScope(brandScopeStr) {
|
|
|
+ if (!brandScopeStr || brandScopeStr === '[]') return [];
|
|
|
+ try {
|
|
|
+ const parsed = JSON.parse(brandScopeStr);
|
|
|
+ return Array.isArray(parsed) ? parsed : [];
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('解析品牌范围数据失败:', error);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 序列化品牌范围数组为JSON字符串
|
|
|
+ * @param {Array<BrandScopeItem>} brandScope 品牌范围数组
|
|
|
+ * @returns {string} JSON字符串
|
|
|
+ */
|
|
|
+ stringifyBrandScope(brandScope) {
|
|
|
+ if (!Array.isArray(brandScope) || brandScope.length === 0) return '[]';
|
|
|
+ try {
|
|
|
+ return JSON.stringify(brandScope);
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('序列化品牌范围数据失败:', error);
|
|
|
+ return '[]';
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析客户黑名单JSON字符串
|
|
|
+ * @param {string} customerBlacklistStr 客户黑名单JSON字符串
|
|
|
+ * @returns {Array<CustomerBlacklistItem>} 客户黑名单数组
|
|
|
+ */
|
|
|
+ parseCustomerBlacklist(customerBlacklistStr) {
|
|
|
+ if (!customerBlacklistStr || customerBlacklistStr === '[]') return [];
|
|
|
+ try {
|
|
|
+ const parsed = JSON.parse(customerBlacklistStr);
|
|
|
+ return Array.isArray(parsed) ? parsed : [];
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('解析客户黑名单数据失败:', error);
|
|
|
+ return [];
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 序列化客户黑名单数组为JSON字符串
|
|
|
+ * @param {Array<CustomerBlacklistItem>} customerBlacklist 客户黑名单数组
|
|
|
+ * @returns {string} JSON字符串
|
|
|
+ */
|
|
|
+ stringifyCustomerBlacklist(customerBlacklist) {
|
|
|
+ if (!Array.isArray(customerBlacklist) || customerBlacklist.length === 0) return '[]';
|
|
|
+ try {
|
|
|
+ return JSON.stringify(customerBlacklist);
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('序列化客户黑名单数据失败:', error);
|
|
|
+ return '[]';
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|
|
|
+
|
|
|
+/**
|
|
|
+ * 创建初始表单数据
|
|
|
+ * @returns {AnnouncementFormModel} 初始表单数据
|
|
|
+ */
|
|
|
+export function createInitialFormData() {
|
|
|
+ return {
|
|
|
+ title: '',
|
|
|
+ content: '',
|
|
|
+ categoryId: '',
|
|
|
+ orgId: 0,
|
|
|
+ orgCode: '',
|
|
|
+ orgName: '',
|
|
|
+ visibleRoles: [],
|
|
|
+ brandScope: [],
|
|
|
+ customerBlacklist: [],
|
|
|
+ remark: '',
|
|
|
+ status: 0
|
|
|
+ };
|
|
|
+}
|
|
|
+
|
|
|
+/**
|
|
|
+ * 公告表单混入
|
|
|
+ */
|
|
|
+export default {
|
|
|
+ props: {
|
|
|
+ /** 是否显示表单 */
|
|
|
+ visible: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ /** 是否为编辑模式 */
|
|
|
+ isEdit: {
|
|
|
+ type: Boolean,
|
|
|
+ default: false
|
|
|
+ },
|
|
|
+ /** 公告ID(编辑时使用) */
|
|
|
+ announcementId: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ },
|
|
|
+ /** 编辑数据(可选,优先级高于announcementId) */
|
|
|
+ editData: {
|
|
|
+ type: Object,
|
|
|
+ default: null
|
|
|
+ },
|
|
|
+ /** 表单标题(可选) */
|
|
|
+ title: {
|
|
|
+ type: String,
|
|
|
+ default: ''
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ data() {
|
|
|
+ return {
|
|
|
+ /** @type {AnnouncementFormModel} 公告表单数据模型 */
|
|
|
+ formData: createInitialFormData(),
|
|
|
+ /** @type {boolean} 保存操作加载状态 */
|
|
|
+ saveLoading: false,
|
|
|
+ /** @type {boolean} 表单加载状态 */
|
|
|
+ formLoading: false,
|
|
|
+ /** @type {Array<{id: string, name: string}>} 分类选项列表 */
|
|
|
+ categoryOptions: [],
|
|
|
+ /** @type {boolean} 分类选项加载状态 */
|
|
|
+ categoryLoading: false,
|
|
|
+ /** @type {Array<{value: number, label: string}>} 角色选项列表 */
|
|
|
+ roleOptions: ROLE_OPTIONS,
|
|
|
+ /** @type {Array<{value: number, label: string, Customer_NAME?: string, Customer_CODE?: string}>} 客户选项列表(用于黑名单选择) */
|
|
|
+ customerOptions: [],
|
|
|
+ /** @type {boolean} 客户选项加载状态 */
|
|
|
+ customerLoading: false,
|
|
|
+ /** @type {Array<{value: string, label: string, name?: string, code?: string}>} 品牌选项列表 */
|
|
|
+ brandOptions: [],
|
|
|
+ /** @type {boolean} 品牌选项加载状态 */
|
|
|
+ brandLoading: false,
|
|
|
+ /** 表单验证规则 */
|
|
|
+ formRules: {
|
|
|
+ title: [
|
|
|
+ { required: true, message: '请输入公告标题', trigger: 'blur' },
|
|
|
+ { min: 1, max: 200, message: '标题长度在 1 到 200 个字符', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ categoryId: [
|
|
|
+ { required: true, message: '请选择公告分类', trigger: 'change' }
|
|
|
+ ],
|
|
|
+ orgId: [
|
|
|
+ { required: true, message: '请选择组织', trigger: 'change' }
|
|
|
+ ],
|
|
|
+ orgCode: [
|
|
|
+ { required: true, message: '组织编码不能为空', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ orgName: [
|
|
|
+ { required: true, message: '组织名称不能为空', trigger: 'blur' }
|
|
|
+ ],
|
|
|
+ visibleRoles: [
|
|
|
+ { required: true, type: 'array', min: 1, message: '请至少选择一个可见角色', trigger: 'change' }
|
|
|
+ ],
|
|
|
+ content: [
|
|
|
+ { required: true, message: '请输入公告内容', trigger: 'blur' }
|
|
|
+ ]
|
|
|
+ },
|
|
|
+ /** 表单配置 */
|
|
|
+ formOption: {}
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ computed: {
|
|
|
+ /**
|
|
|
+ * 表单标题
|
|
|
+ * @returns {string} 表单标题
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ formTitle() {
|
|
|
+ if (this.title) return this.title;
|
|
|
+ return this.isEdit ? '编辑公告' : '新增公告';
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 是否可编辑
|
|
|
+ * @returns {boolean} 是否可编辑
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ canEdit() {
|
|
|
+ return !this.formLoading && !this.saveLoading;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ watch: {
|
|
|
+ /**
|
|
|
+ * 监听表单显示状态变化
|
|
|
+ * @param {boolean} newVal 新值
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ visible: {
|
|
|
+ handler(newVal) {
|
|
|
+ if (newVal) {
|
|
|
+ // 初始化表单数据
|
|
|
+ this.formData = createInitialFormData();
|
|
|
+ // 加载分类选项
|
|
|
+ this.loadCategoryOptions();
|
|
|
+ } else {
|
|
|
+ // 重置表单
|
|
|
+ if (this.$refs.form && typeof this.$refs.form.resetFields === 'function') {
|
|
|
+ this.$refs.form.resetFields();
|
|
|
+ }
|
|
|
+ }
|
|
|
+ },
|
|
|
+ immediate: true
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 监听编辑数据变化
|
|
|
+ * @param {NoticeRecord|null} newVal 新值
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ editData: {
|
|
|
+ handler(newVal) {
|
|
|
+ if (newVal && this.visible) {
|
|
|
+ this.formData = {
|
|
|
+ id: newVal.id || '',
|
|
|
+ title: newVal.title || '',
|
|
|
+ content: newVal.content || '',
|
|
|
+ categoryId: newVal.categoryId || '',
|
|
|
+ categoryName: newVal.categoryName,
|
|
|
+ orgId: Number(newVal.orgId || 0),
|
|
|
+ orgCode: newVal.orgCode || '',
|
|
|
+ orgName: newVal.orgName || '',
|
|
|
+ visibleRoles: dataConverter.parseVisibleRoles(newVal.visibleRoles || 0),
|
|
|
+ brandScope: dataConverter.parseBrandScope(newVal.brandScope || '[]'),
|
|
|
+ customerBlacklist: dataConverter.parseCustomerBlacklist(newVal.customerBlacklist || '[]'),
|
|
|
+ remark: newVal.remark || '',
|
|
|
+ status: Number(newVal.status || 0),
|
|
|
+ createTime: newVal.createTime,
|
|
|
+ updateTime: newVal.updateTime
|
|
|
+ };
|
|
|
+ }
|
|
|
+ },
|
|
|
+ deep: true,
|
|
|
+ immediate: true
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ methods: {
|
|
|
+ // 数据转换方法
|
|
|
+ /**
|
|
|
+ * API数据转换为表单模型
|
|
|
+ * @param {NoticeRecord} apiData API数据
|
|
|
+ * @returns {AnnouncementFormModel} 表单模型
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ apiToFormModel: dataConverter.apiToFormModel,
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 表单模型转换为API数据
|
|
|
+ * @param {AnnouncementFormModel} formModel 表单模型
|
|
|
+ * @returns {NoticeFormData} API数据
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ formModelToApi: dataConverter.formModelToApi,
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 解析角色掩码
|
|
|
+ * @param {string | number} rolesMask 角色掩码
|
|
|
+ * @returns {Array<RoleType>} 角色类型数组
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ parseVisibleRoles: dataConverter.parseVisibleRoles.bind(dataConverter),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 计算角色掩码
|
|
|
+ * @param {Array<RoleType>} roles 角色类型数组
|
|
|
+ * @returns {number} 角色掩码值
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ calculateRolesMask: dataConverter.calculateRolesMask.bind(dataConverter),
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 客户黑名单字符串化
|
|
|
+ * @param {Array<CustomerBlacklistItem>} customerBlacklist 客户黑名单
|
|
|
+ * @returns {string} 字符串化结果
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ stringifyCustomerBlacklist: dataConverter.stringifyCustomerBlacklist,
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 品牌范围字符串化
|
|
|
+ * @param {Array<BrandScopeItem>} brandScope 品牌范围
|
|
|
+ * @returns {string} 字符串化结果
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ stringifyBrandScope: dataConverter.stringifyBrandScope,
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 创建初始表单数据
|
|
|
+ * @returns {AnnouncementFormModel} 初始表单数据
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ createInitialFormData,
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化表单数据
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ * @returns {Promise<void>}
|
|
|
+ */
|
|
|
+ async initFormData() {
|
|
|
+ this.formData = this.createInitialFormData();
|
|
|
+
|
|
|
+ if (this.editData) {
|
|
|
+ // 优先使用传入的编辑数据
|
|
|
+ this.formData = dataConverter.apiToFormModel(this.editData);
|
|
|
+ } else if (this.isEdit && this.announcementId) {
|
|
|
+ // 根据ID加载公告详情
|
|
|
+ await this.loadAnnouncementDetail();
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 初始化表单配置
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ initFormOption() {
|
|
|
+ this.formOption = {
|
|
|
+ submitBtn: false,
|
|
|
+ emptyBtn: false,
|
|
|
+ column: [
|
|
|
+ {
|
|
|
+ prop: 'title',
|
|
|
+ label: '公告标题',
|
|
|
+ type: 'input',
|
|
|
+ span: 24,
|
|
|
+ rules: this.formRules.title
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'categoryId',
|
|
|
+ label: '公告分类',
|
|
|
+ type: 'select',
|
|
|
+ span: 12,
|
|
|
+ dicData: this.categoryOptions,
|
|
|
+ props: {
|
|
|
+ label: 'name',
|
|
|
+ value: 'id'
|
|
|
+ },
|
|
|
+ rules: this.formRules.categoryId
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'status',
|
|
|
+ label: '状态',
|
|
|
+ type: 'select',
|
|
|
+ span: 12,
|
|
|
+ dicData: STATUS_OPTIONS,
|
|
|
+ props: {
|
|
|
+ label: 'label',
|
|
|
+ value: 'value'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'orgName',
|
|
|
+ label: '组织名称',
|
|
|
+ type: 'input',
|
|
|
+ span: 8,
|
|
|
+ rules: this.formRules.orgName
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'orgCode',
|
|
|
+ label: '组织编码',
|
|
|
+ type: 'input',
|
|
|
+ span: 8,
|
|
|
+ rules: this.formRules.orgCode
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'orgId',
|
|
|
+ label: '组织ID',
|
|
|
+ type: 'number',
|
|
|
+ span: 8,
|
|
|
+ rules: this.formRules.orgId
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'visibleRoles',
|
|
|
+ label: '可见角色',
|
|
|
+ type: 'checkbox',
|
|
|
+ span: 24,
|
|
|
+ dicData: this.roleOptions,
|
|
|
+ props: {
|
|
|
+ label: 'label',
|
|
|
+ value: 'value'
|
|
|
+ },
|
|
|
+ rules: this.formRules.visibleRoles
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'brandScope',
|
|
|
+ label: '品牌范围',
|
|
|
+ type: 'select',
|
|
|
+ span: 12,
|
|
|
+ multiple: true,
|
|
|
+ filterable: true,
|
|
|
+ remote: true,
|
|
|
+ remoteMethod: this.remoteSearchBrands,
|
|
|
+ loading: this.brandLoading,
|
|
|
+ dicData: this.brandOptions,
|
|
|
+ props: {
|
|
|
+ label: 'label',
|
|
|
+ value: 'value'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'customerBlacklist',
|
|
|
+ label: '客户黑名单',
|
|
|
+ type: 'select',
|
|
|
+ span: 12,
|
|
|
+ multiple: true,
|
|
|
+ filterable: true,
|
|
|
+ remote: true,
|
|
|
+ remoteMethod: this.remoteSearchCustomers,
|
|
|
+ loading: this.customerLoading,
|
|
|
+ dicData: this.customerOptions,
|
|
|
+ props: {
|
|
|
+ label: 'label',
|
|
|
+ value: 'value'
|
|
|
+ }
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'content',
|
|
|
+ label: '公告内容',
|
|
|
+ type: 'textarea',
|
|
|
+ span: 24,
|
|
|
+ minRows: 4,
|
|
|
+ maxRows: 8,
|
|
|
+ rules: this.formRules.content
|
|
|
+ },
|
|
|
+ {
|
|
|
+ prop: 'remark',
|
|
|
+ label: '备注',
|
|
|
+ type: 'textarea',
|
|
|
+ span: 24,
|
|
|
+ minRows: 2,
|
|
|
+ maxRows: 4
|
|
|
+ }
|
|
|
+ ]
|
|
|
+ };
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加载公告详情
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ * @returns {Promise<void>}
|
|
|
+ */
|
|
|
+ async loadAnnouncementDetail() {
|
|
|
+ if (!this.announcementId) return;
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.formLoading = true;
|
|
|
+ const response = await getAnnouncement(this.announcementId);
|
|
|
+ if (response.data && response.data.success) {
|
|
|
+ /** @type {NoticeRecord} */
|
|
|
+ const apiData = response.data.data;
|
|
|
+ this.formData = dataConverter.apiToFormModel(apiData);
|
|
|
+ this.$emit('loaded');
|
|
|
+ } else {
|
|
|
+ this.$message.error(response.data?.message || '加载公告详情失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载公告详情失败:', error);
|
|
|
+ this.$message.error('加载公告详情失败');
|
|
|
+ } finally {
|
|
|
+ this.formLoading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 加载分类选项
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ * @returns {Promise<void>}
|
|
|
+ */
|
|
|
+ async loadCategoryOptions() {
|
|
|
+ try {
|
|
|
+ this.categoryLoading = true;
|
|
|
+ const response = await getCategoryList();
|
|
|
+ if (response.data && response.data.success) {
|
|
|
+ /** @type {Array<{id: string, name: string, label: string, value: string}>} */
|
|
|
+ this.categoryOptions = (response.data.data || []).map(item => ({
|
|
|
+ id: String(item.id),
|
|
|
+ name: String(item.name),
|
|
|
+ label: String(item.name),
|
|
|
+ value: String(item.id)
|
|
|
+ }));
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('加载分类选项失败:', error);
|
|
|
+ } finally {
|
|
|
+ this.categoryLoading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理表单提交
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ * @returns {Promise<void>}
|
|
|
+ */
|
|
|
+ async handleSubmit() {
|
|
|
+ try {
|
|
|
+ // 表单验证
|
|
|
+ const isValid = await this.validateForm();
|
|
|
+ if (!isValid) return;
|
|
|
+
|
|
|
+ this.saveLoading = true;
|
|
|
+
|
|
|
+ // 转换数据格式
|
|
|
+ /** @type {NoticeFormData} */
|
|
|
+ const submitData = dataConverter.formModelToApi(this.formData);
|
|
|
+
|
|
|
+ // 提交数据
|
|
|
+ const response = await this.submitAnnouncementData(submitData);
|
|
|
+
|
|
|
+ if (response.data && response.data.success) {
|
|
|
+ this.$message.success(this.isEdit ? '更新成功' : '创建成功');
|
|
|
+ this.$emit('submit-success', this.formData);
|
|
|
+ this.$emit('save-success', this.formData);
|
|
|
+ this.handleCancel();
|
|
|
+ } else {
|
|
|
+ this.$message.error(response.data?.message || '保存失败');
|
|
|
+ }
|
|
|
+ } catch (error) {
|
|
|
+ console.error('提交表单失败:', error);
|
|
|
+ this.$message.error('保存失败');
|
|
|
+ } finally {
|
|
|
+ this.saveLoading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 重置表单
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ handleReset() {
|
|
|
+ this.formData = this.createInitialFormData();
|
|
|
+ this.customerOptions = [];
|
|
|
+ this.brandOptions = [];
|
|
|
+ this.$emit('reset');
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 取消操作
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ handleCancel() {
|
|
|
+ this.$emit('cancel');
|
|
|
+ this.$emit('update:visible', false);
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 提交公告数据
|
|
|
+ * @param {NoticeFormData} submitData 提交数据
|
|
|
+ * @returns {Promise<import('axios').AxiosResponse<any>>} API响应
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ async submitAnnouncementData(submitData) {
|
|
|
+ if (this.isEdit) {
|
|
|
+ return await updateAnnouncement(this.announcementId, submitData);
|
|
|
+ } else {
|
|
|
+ return await addAnnouncement(submitData);
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 验证表单
|
|
|
+ * @returns {Promise<boolean>} 验证结果
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ async validateForm() {
|
|
|
+ try {
|
|
|
+ const form = this.$refs.form;
|
|
|
+ if (form && typeof form.validate === 'function') {
|
|
|
+ await form.validate();
|
|
|
+ return true;
|
|
|
+ }
|
|
|
+ return false;
|
|
|
+ } catch (error) {
|
|
|
+ console.warn('表单验证失败:', error);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 远程搜索客户
|
|
|
+ * @param {string} query 搜索关键词
|
|
|
+ * @returns {Promise<void>}
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ async remoteSearchCustomers(query) {
|
|
|
+ if (!query || query.length < 2) {
|
|
|
+ this.customerOptions = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.customerLoading = true;
|
|
|
+ // TODO: 实现客户搜索API调用
|
|
|
+ this.customerOptions = [];
|
|
|
+ } catch (error) {
|
|
|
+ console.error('搜索客户失败:', error);
|
|
|
+ } finally {
|
|
|
+ this.customerLoading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 远程搜索品牌
|
|
|
+ * @param {string} query 搜索关键词
|
|
|
+ * @returns {Promise<void>}
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ async remoteSearchBrands(query) {
|
|
|
+ if (!query || query.length < 2) {
|
|
|
+ this.brandOptions = [];
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ this.brandLoading = true;
|
|
|
+ // TODO: 实现品牌搜索API调用
|
|
|
+ this.brandOptions = [];
|
|
|
+ } catch (error) {
|
|
|
+ console.error('搜索品牌失败:', error);
|
|
|
+ } finally {
|
|
|
+ this.brandLoading = false;
|
|
|
+ }
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理客户黑名单变化
|
|
|
+ * @param {Array<CustomerOption>} selectedCustomers 选中的客户
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ handleCustomerBlacklistChange(selectedCustomers) {
|
|
|
+ /** @type {Array<CustomerBlacklistItem>} */
|
|
|
+ this.formData.customerBlacklist = selectedCustomers.map(customer => {
|
|
|
+ // 从label中提取客户代码
|
|
|
+ const codeMatch = customer.label ? customer.label.match(/\(([^)]+)\)$/) : null;
|
|
|
+ const code = codeMatch ? codeMatch[1] : customer.Customer_CODE || '';
|
|
|
+
|
|
|
+ return {
|
|
|
+ id: typeof customer.value === 'number' ? customer.value : parseInt(String(customer.value), 10),
|
|
|
+ Customer_NAME: customer.Customer_NAME || (customer.label ? customer.label.replace(/\([^)]+\)$/, '').trim() : ''),
|
|
|
+ Customer_CODE: code
|
|
|
+ };
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 处理品牌范围变化
|
|
|
+ * @param {Array<BrandOption>} selectedBrands 选中的品牌
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ handleBrandScopeChange(selectedBrands) {
|
|
|
+ /** @type {Array<BrandScopeItem>} */
|
|
|
+ this.formData.brandScope = selectedBrands.map(brand => {
|
|
|
+ // 从label中提取品牌代码
|
|
|
+ const codeMatch = brand.label ? brand.label.match(/\(([^)]+)\)$/) : null;
|
|
|
+ const code = codeMatch ? codeMatch[1] : brand.code || '';
|
|
|
+
|
|
|
+ return {
|
|
|
+ id: String(brand.value),
|
|
|
+ name: brand.name || (brand.label ? brand.label.replace(/\([^)]+\)$/, '').trim() : ''),
|
|
|
+ code: code
|
|
|
+ };
|
|
|
+ });
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清空客户选项
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ clearCustomerOptions() {
|
|
|
+ this.customerOptions = [];
|
|
|
+ },
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 清空品牌选项
|
|
|
+ * @this {AnnouncementFormMixinComponent & Vue}
|
|
|
+ */
|
|
|
+ clearBrandOptions() {
|
|
|
+ this.brandOptions = [];
|
|
|
+ }
|
|
|
+ }
|
|
|
+};
|