diff --git a/src/components/custom/business-attachment-uploader.vue b/src/components/custom/business-attachment-uploader.vue index 0cc1767..8c1caa0 100644 --- a/src/components/custom/business-attachment-uploader.vue +++ b/src/components/custom/business-attachment-uploader.vue @@ -31,7 +31,7 @@ const props = withDefaults(defineProps(), { const model = defineModel({ default: () => [] }); /** 给用户看的简短分类(hint 行展示) */ -const ALLOWED_EXTENSIONS_HINT = '支持 PDF、Word、Excel、PPT、TXT/MD/CSV、图片、ZIP/RAR/7Z、MP3/MP4'; +const ALLOWED_EXTENSIONS_HINT = '支持 PDF、Word、Excel、PPT、TXT/MD/CSV、图片、ZIP/RAR/7Z、MP3/MP4、SQL/JSON/XML'; // 与后端 AttachmentValidator 白/黑名单保持一致(5.16) const ALLOWED_EXTENSIONS = new Set([ @@ -55,7 +55,10 @@ const ALLOWED_EXTENSIONS = new Set([ 'rar', '7z', 'mp4', - 'mp3' + 'mp3', + 'sql', + 'xml', + 'json' ]); const FORBIDDEN_EXTENSIONS = new Set([ diff --git a/src/locales/langs/zh-cn.ts b/src/locales/langs/zh-cn.ts index 44769d8..20fd538 100644 --- a/src/locales/langs/zh-cn.ts +++ b/src/locales/langs/zh-cn.ts @@ -661,7 +661,7 @@ const local: App.I18n.Schema = { }, pwd: { required: '请输入密码', - invalid: '密码格式不正确,6-18位字符,包含字母、数字、下划线' + invalid: '密码格式不正确,4-30位字符,包含字母、数字、下划线' }, confirmPwd: { required: '请输入确认密码', diff --git a/src/service/api/product.ts b/src/service/api/product.ts index f4647e7..ada657b 100644 --- a/src/service/api/product.ts +++ b/src/service/api/product.ts @@ -18,6 +18,10 @@ type ProductResponse = Omit & { + id: string | number; +}; + type ProductPageResponse = Api.Product.PageResult; type ProductActivityTimelineItemResponse = Omit< @@ -46,6 +50,13 @@ function normalizeProduct(product: ProductResponse): Api.Product.Product { }; } +function normalizeProductOption(option: ProductOptionResponse): Api.Product.ProductOption { + return { + ...option, + id: normalizeStringId(option.id) + }; +} + function normalizeOccurredAt(occurredAt: number | string) { const value = Number(occurredAt); @@ -109,6 +120,19 @@ export async function fetchGetProductPage(params?: Api.Product.ProductSearchPara })); } +/** 获取可绑定产品下拉选项 */ +export async function fetchGetProductOptions() { + const result = await request({ + ...safeJsonRequestConfig, + url: `${PRODUCT_PREFIX}/options`, + method: 'get' + }); + + return mapServiceResult(result as ServiceRequestResult, data => + (data ?? []).map(normalizeProductOption) + ); +} + type ProductOverviewSummaryResponse = Omit & { /** 后端 overview-summary 升级(total/items)灰度期间可能缺省,适配层兜底 */ total?: number | null; diff --git a/src/typings/api/product.d.ts b/src/typings/api/product.d.ts index 4a311e9..6587855 100644 --- a/src/typings/api/product.d.ts +++ b/src/typings/api/product.d.ts @@ -92,6 +92,17 @@ declare namespace Api { lastStatusReason?: string | null; } + interface ProductOption { + /** 产品 ID */ + id: string; + /** 产品编码 */ + code: string; + /** 产品名称 */ + name: string; + /** 产品方向字典值 */ + directionCode: string; + } + interface ProductLifecycleAction { actionCode: ProductStatusActionCode; actionName: string; @@ -216,6 +227,7 @@ declare namespace Api { interface DeleteProductParams { id: string; productName: string; + confirmText: string; reason: string; } diff --git a/src/views/personal-center/work-report/monthly/modules/fill-page.vue b/src/views/personal-center/work-report/monthly/modules/fill-page.vue index 7e40c5e..3dc32e3 100644 --- a/src/views/personal-center/work-report/monthly/modules/fill-page.vue +++ b/src/views/personal-center/work-report/monthly/modules/fill-page.vue @@ -558,7 +558,9 @@ function resolveTaskMetrics(metricsText: string, fallback?: Partial /^P?\d+$/iu.test(item)); const priority = normalizePriorityCode(priorityText || (useFallback ? fallback?.priority : undefined)); - const progressText = metricsText.match(/进度\s*(\d+(?:\.\d+)?)%/u)?.[1]; + const progressText = + metricsText.match(/进度\s*(\d+(?:\.\d+)?)%/u)?.[1] || + metricParts.find(item => /^\d+(?:\.\d+)?%$/u.test(item))?.replace(/%$/u, ''); const hoursText = metricsText.match(/(\d+(?:\.\d+)?)h/u)?.[1]; return { @@ -591,7 +593,7 @@ function toReviewItem(item: Api.WorkReport.Common.PersonalReportReviewItem): Rev : escapeHtml(item.contentText || '').replace(/\n/g, '
') || EMPTY_TEXT, contentSections, reflection: item.reflectionText || '', - removable: false, + removable: true, source: item }; } @@ -728,6 +730,13 @@ function removePlanItem(index: number) { if (item?.sourceIndex !== undefined) props.model.planItems.splice(item.sourceIndex, 1); } +function removeReviewItem(index: number) { + const item = reviewItems.value[index]; + if (!item?.source) return; + const sourceIndex = props.model.reviewItems.indexOf(item.source); + if (sourceIndex >= 0) props.model.reviewItems.splice(sourceIndex, 1); +} + function focusEditField(key: string) { activeEditField.value = key; } @@ -736,9 +745,19 @@ function blurEditField(key: string) { if (activeEditField.value === key) activeEditField.value = ''; } +function isStructuredEditorUnchanged(text: string, sections: StructuredSection[] | undefined, showHours = false) { + if (!sections?.length) return false; + return normalizeEditorText(text) === createStructuredTextV2(sections, showHours); +} + function syncRichContent(item: ReviewItem, event: Event) { const target = event.currentTarget as HTMLElement; if (!item.source) return; + if (isStructuredEditorUnchanged(target.innerText, item.contentSections, true)) { + item.source.contentJson = createSectionsJson(item.contentSections || []); + item.source.contentText = createStructuredTextV2(item.contentSections || [], true); + return; + } const sections = parseStructuredSectionsFromEditorV2( target, item.contentSections || [], @@ -751,6 +770,11 @@ function syncRichContent(item: ReviewItem, event: Event) { function syncRichTarget(item: PlanItem, event: Event) { const target = event.currentTarget as HTMLElement; if (!item.source) return; + if (isStructuredEditorUnchanged(target.innerText, item.targetSections)) { + item.source.targetJson = createSectionsJson(item.targetSections || []); + item.source.targetText = createStructuredTextV2(item.targetSections || []); + return; + } const sections = parseStructuredSectionsFromEditorV2( target, item.targetSections || [], @@ -836,7 +860,7 @@ function syncRichSupport(item: PlanItem, event: Event) { confirm-button-text="删除" cancel-button-text="取消" width="220" - @confirm="reviewItems.splice(index, 1)" + @confirm="removeReviewItem(index)" > + + + + + + - - - - - -