frequencyday的值约定
This commit is contained in:
@@ -706,3 +706,35 @@
|
|||||||
### 5. 验证与测试
|
### 5. 验证与测试
|
||||||
|
|
||||||
- `npm run dev`:终端出现 Network URL;同网段可打开页面。
|
- `npm run dev`:终端出现 Network URL;同网段可打开页面。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 补充:frequencyDay 与后端约定对齐(2026-10-10)
|
||||||
|
|
||||||
|
### 1. 改动概览
|
||||||
|
|
||||||
|
- **背景与目标**:后端 `WEEKLY` 支持 `1`–`7`(ISO 周一=1)、英文全名/三字码;`MONTHLY` 支持 `1`–`31`(可带前导零);`DAILY` 不传 `frequencyDay`。前端提交已用 `MON`…`SUN` 与 `1`…`31`,需兼容接口回显。
|
||||||
|
- **涉及模块**:`src/utils/frequencyDay.ts`(新增)、`scheduleInvestmentPlanMapper.ts`、`RecurringInvestmentView.vue`。
|
||||||
|
- **改动类型**:缺陷修复 / 行为对齐。
|
||||||
|
|
||||||
|
### 2. 方法级改动分析
|
||||||
|
|
||||||
|
| 位置 | 差异 |
|
||||||
|
|------|------|
|
||||||
|
| `frequencyDay.ts` | `normalizeWeeklyDayToCode`、`normalizeMonthlyDay`、`formatFrequencyDayLabel`。 |
|
||||||
|
| `scheduleInvestmentPlanMapper.ts` | 列表映射时将 `frequencyDay` 规范为表单/i18n 可用值。 |
|
||||||
|
| `RecurringInvestmentView.vue` | 计划列表、详情频率、确认预览统一走 `formatFrequencyDayLabel`。 |
|
||||||
|
|
||||||
|
### 3. 调用方与影响范围
|
||||||
|
|
||||||
|
- 创建/预览请求体:`DAILY` 不传 `frequencyDay`;`WEEKLY`/`MONTHLY` 经 `frequencyDayForApi` 仅提交数字字符串(周 `1`–`7`,月 `1`–`31`)。
|
||||||
|
- **破坏性变更**:否。
|
||||||
|
|
||||||
|
### 4. 风险与回滚
|
||||||
|
|
||||||
|
- **风险级别**:低。
|
||||||
|
- **回滚方式是否简单**:是。
|
||||||
|
|
||||||
|
### 5. 验证与测试
|
||||||
|
|
||||||
|
- `npm run build`。
|
||||||
|
|||||||
@@ -225,3 +225,11 @@
|
|||||||
- 创建表单在选定交易所与资产后请求 `POST /trade/schedule-investment/options/ct-time`(`productCode` 与创建计划一致:`productCodeUnique` 或 `code`)。
|
- 创建表单在选定交易所与资产后请求 `POST /trade/schedule-investment/options/ct-time`(`productCode` 与创建计划一致:`productCodeUnique` 或 `code`)。
|
||||||
- 偏移字段改为 `el-select`(`displayName` / `offsetMinutes`);未选资产时禁用并提示先选交易所与资产。
|
- 偏移字段改为 `el-select`(`displayName` / `offsetMinutes`);未选资产时禁用并提示先选交易所与资产。
|
||||||
- 详见 `IMPACT_ANALYSIS.md` 本节补充;验证:`npm run build`。
|
- 详见 `IMPACT_ANALYSIS.md` 本节补充;验证:`npm run build`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 补充:frequencyDay 回显归一化(2026-10-10)
|
||||||
|
|
||||||
|
- 新增 `src/utils/frequencyDay.ts`,将接口 `1`/`MONDAY`/`MON` 等 weekly 值与带前导零的 monthly 值规范后用于列表与详情展示。
|
||||||
|
- 创建/预览提交:`WEEKLY` 为 `1`–`7`,`MONTHLY` 为 `1`–`31`;`DAILY` 不传 `frequencyDay`。界面仍显示星期中文名。
|
||||||
|
- 验证:`npm run build`;参见 `IMPACT_ANALYSIS.md` 对应补充。
|
||||||
|
|||||||
Vendored
-1
@@ -20,7 +20,6 @@ declare module 'vue' {
|
|||||||
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
ElDatePicker: typeof import('element-plus/es')['ElDatePicker']
|
||||||
ElDialog: typeof import('element-plus/es')['ElDialog']
|
ElDialog: typeof import('element-plus/es')['ElDialog']
|
||||||
ElInput: typeof import('element-plus/es')['ElInput']
|
ElInput: typeof import('element-plus/es')['ElInput']
|
||||||
ElInputNumber: typeof import('element-plus/es')['ElInputNumber']
|
|
||||||
ElOption: typeof import('element-plus/es')['ElOption']
|
ElOption: typeof import('element-plus/es')['ElOption']
|
||||||
ElRow: typeof import('element-plus/es')['ElRow']
|
ElRow: typeof import('element-plus/es')['ElRow']
|
||||||
ElSelect: typeof import('element-plus/es')['ElSelect']
|
ElSelect: typeof import('element-plus/es')['ElSelect']
|
||||||
|
|||||||
@@ -0,0 +1,145 @@
|
|||||||
|
import type { FrequencyType, WeekdayCode } from '@/types/scheduleInvestment'
|
||||||
|
import { WEEKDAY_CODES } from '@/types/scheduleInvestment'
|
||||||
|
|
||||||
|
/** ISO:1 = 周一 … 7 = 周日(与后端 resolveWeeklyDay 一致) */
|
||||||
|
export const WEEKLY_ISO_DAY_VALUES = ['1', '2', '3', '4', '5', '6', '7'] as const
|
||||||
|
|
||||||
|
const ISO_WEEKDAY_TO_CODE: Record<number, WeekdayCode> = {
|
||||||
|
1: 'MON',
|
||||||
|
2: 'TUE',
|
||||||
|
3: 'WED',
|
||||||
|
4: 'THU',
|
||||||
|
5: 'FRI',
|
||||||
|
6: 'SAT',
|
||||||
|
7: 'SUN',
|
||||||
|
}
|
||||||
|
|
||||||
|
const FULL_WEEKDAY_TO_CODE: Record<string, WeekdayCode> = {
|
||||||
|
MONDAY: 'MON',
|
||||||
|
TUESDAY: 'TUE',
|
||||||
|
WEDNESDAY: 'WED',
|
||||||
|
THURSDAY: 'THU',
|
||||||
|
FRIDAY: 'FRI',
|
||||||
|
SATURDAY: 'SAT',
|
||||||
|
SUNDAY: 'SUN',
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 将接口返回的 weekly frequencyDay(1–7 / MONDAY / MON 等)规范为三字码,供表单与 i18n 使用。
|
||||||
|
*/
|
||||||
|
export function normalizeWeeklyDayToCode(raw: string | null | undefined): WeekdayCode | undefined {
|
||||||
|
if (raw == null) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const trimmed = raw.trim()
|
||||||
|
if (!trimmed) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
const upper = trimmed.toUpperCase()
|
||||||
|
if ((WEEKDAY_CODES as readonly string[]).includes(upper)) {
|
||||||
|
return upper as WeekdayCode
|
||||||
|
}
|
||||||
|
|
||||||
|
const fromFull = FULL_WEEKDAY_TO_CODE[upper]
|
||||||
|
if (fromFull) {
|
||||||
|
return fromFull
|
||||||
|
}
|
||||||
|
|
||||||
|
const iso = Number.parseInt(trimmed, 10)
|
||||||
|
if (!Number.isNaN(iso) && iso >= 1 && iso <= 7) {
|
||||||
|
return ISO_WEEKDAY_TO_CODE[iso]
|
||||||
|
}
|
||||||
|
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 规范为 API/表单用的 weekly 数字字符串 `"1"`–`"7"`。 */
|
||||||
|
export function isoWeekdayToCode(iso: string): WeekdayCode | undefined {
|
||||||
|
const n = Number.parseInt(iso, 10)
|
||||||
|
if (Number.isNaN(n) || n < 1 || n > 7) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
return ISO_WEEKDAY_TO_CODE[n]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeWeeklyDayToIso(raw: string | null | undefined): string | undefined {
|
||||||
|
const code = normalizeWeeklyDayToCode(raw)
|
||||||
|
if (!code) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const iso = (Object.entries(ISO_WEEKDAY_TO_CODE) as [string, WeekdayCode][]).find(
|
||||||
|
([, c]) => c === code,
|
||||||
|
)?.[0]
|
||||||
|
return iso
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 每月执行日:接口可带前导零(如 "09"),表单选项值为 "1"–"31"。 */
|
||||||
|
export function normalizeMonthlyDay(raw: string | null | undefined): string | undefined {
|
||||||
|
if (raw == null) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const trimmed = raw.trim()
|
||||||
|
if (!trimmed) {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
const day = Number.parseInt(trimmed, 10)
|
||||||
|
if (Number.isNaN(day) || day < 1 || day > 31) {
|
||||||
|
return trimmed
|
||||||
|
}
|
||||||
|
return String(day)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function normalizeFrequencyDay(
|
||||||
|
frequencyType: FrequencyType,
|
||||||
|
raw: string | null | undefined,
|
||||||
|
): string | undefined {
|
||||||
|
if (raw == null || raw === '') {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (frequencyType === 'WEEKLY') {
|
||||||
|
return normalizeWeeklyDayToIso(raw)
|
||||||
|
}
|
||||||
|
if (frequencyType === 'MONTHLY') {
|
||||||
|
return normalizeMonthlyDay(raw)
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
/** 创建/预览请求体:WEEKLY、MONTHLY 仅传数字字符串。 */
|
||||||
|
export function frequencyDayForApi(
|
||||||
|
frequencyType: FrequencyType,
|
||||||
|
raw: string | null | undefined,
|
||||||
|
): string | undefined {
|
||||||
|
if (raw == null || raw === '') {
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
if (frequencyType === 'WEEKLY') {
|
||||||
|
return normalizeWeeklyDayToIso(raw)
|
||||||
|
}
|
||||||
|
if (frequencyType === 'MONTHLY') {
|
||||||
|
return normalizeMonthlyDay(raw)
|
||||||
|
}
|
||||||
|
return undefined
|
||||||
|
}
|
||||||
|
|
||||||
|
type TranslateFn = (key: string, params?: Record<string, unknown>) => string
|
||||||
|
|
||||||
|
export function formatFrequencyDayLabel(
|
||||||
|
frequencyType: FrequencyType,
|
||||||
|
frequencyDay: string,
|
||||||
|
t: TranslateFn,
|
||||||
|
): string {
|
||||||
|
if (frequencyType === 'WEEKLY') {
|
||||||
|
const code = normalizeWeeklyDayToCode(frequencyDay)
|
||||||
|
if (code) {
|
||||||
|
return t(`recurring.weekday.${code}`)
|
||||||
|
}
|
||||||
|
return frequencyDay
|
||||||
|
}
|
||||||
|
if (frequencyType === 'MONTHLY') {
|
||||||
|
const day = normalizeMonthlyDay(frequencyDay) ?? frequencyDay
|
||||||
|
return t('recurring.monthDayOption', { day })
|
||||||
|
}
|
||||||
|
return frequencyDay
|
||||||
|
}
|
||||||
@@ -7,6 +7,7 @@ import type {
|
|||||||
ScheduleInvestmentCreateBody,
|
ScheduleInvestmentCreateBody,
|
||||||
} from '@/types/scheduleInvestment'
|
} from '@/types/scheduleInvestment'
|
||||||
import type { IntervalKey } from '@/types/recurringInvestment'
|
import type { IntervalKey } from '@/types/recurringInvestment'
|
||||||
|
import { frequencyDayForApi } from '@/utils/frequencyDay'
|
||||||
|
|
||||||
export interface ScheduleFormPayloadInput {
|
export interface ScheduleFormPayloadInput {
|
||||||
exchangeCode: string
|
exchangeCode: string
|
||||||
@@ -71,7 +72,10 @@ export function buildScheduleInvestmentCreateBody(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (input.frequencyType === 'WEEKLY' || input.frequencyType === 'MONTHLY') {
|
if (input.frequencyType === 'WEEKLY' || input.frequencyType === 'MONTHLY') {
|
||||||
body.frequencyDay = input.frequencyDay
|
const day = frequencyDayForApi(input.frequencyType, input.frequencyDay)
|
||||||
|
if (day) {
|
||||||
|
body.frequencyDay = day
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (endType === 'DATE') {
|
if (endType === 'DATE') {
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import type {
|
|||||||
ScheduleEndType,
|
ScheduleEndType,
|
||||||
ScheduleInvestmentPlanRecord,
|
ScheduleInvestmentPlanRecord,
|
||||||
} from '@/types/scheduleInvestment'
|
} from '@/types/scheduleInvestment'
|
||||||
|
import { normalizeFrequencyDay } from '@/utils/frequencyDay'
|
||||||
import { frequencyTypeToInterval } from '@/utils/scheduleInvestmentPayload'
|
import { frequencyTypeToInterval } from '@/utils/scheduleInvestmentPayload'
|
||||||
|
|
||||||
function parseDecimal(value: string | null | undefined): number {
|
function parseDecimal(value: string | null | undefined): number {
|
||||||
@@ -90,6 +91,7 @@ export function mapSchedulePlanRecordToRecurringPlan(
|
|||||||
record.investmentType === 'QUANTITY' ? parseDecimal(record.qtyPerExecution) : null
|
record.investmentType === 'QUANTITY' ? parseDecimal(record.qtyPerExecution) : null
|
||||||
const endFields = resolveEndFields(record)
|
const endFields = resolveEndFields(record)
|
||||||
const frequencyType = record.frequencyType as FrequencyType
|
const frequencyType = record.frequencyType as FrequencyType
|
||||||
|
const frequencyDay = normalizeFrequencyDay(frequencyType, record.frequencyDay ?? undefined)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
id: record.id,
|
id: record.id,
|
||||||
@@ -99,7 +101,7 @@ export function mapSchedulePlanRecordToRecurringPlan(
|
|||||||
quantity,
|
quantity,
|
||||||
investmentType: record.investmentType,
|
investmentType: record.investmentType,
|
||||||
frequencyType,
|
frequencyType,
|
||||||
frequencyDay: record.frequencyDay ?? undefined,
|
frequencyDay,
|
||||||
interval: frequencyTypeToInterval(frequencyType),
|
interval: frequencyTypeToInterval(frequencyType),
|
||||||
startDate: record.startDate,
|
startDate: record.startDate,
|
||||||
...endFields,
|
...endFields,
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import {
|
|||||||
FREQUENCY_TYPE_KEYS,
|
FREQUENCY_TYPE_KEYS,
|
||||||
INVESTMENT_TYPE_KEYS,
|
INVESTMENT_TYPE_KEYS,
|
||||||
ORDER_PRICE_TYPE_KEYS,
|
ORDER_PRICE_TYPE_KEYS,
|
||||||
WEEKDAY_CODES,
|
|
||||||
type FrequencyType,
|
type FrequencyType,
|
||||||
type InvestmentType,
|
type InvestmentType,
|
||||||
type OrderPriceType,
|
type OrderPriceType,
|
||||||
@@ -53,6 +52,12 @@ import {
|
|||||||
mapApiPlanStatus,
|
mapApiPlanStatus,
|
||||||
mapSchedulePlanRecordToRecurringPlan,
|
mapSchedulePlanRecordToRecurringPlan,
|
||||||
} from '@/utils/scheduleInvestmentPlanMapper'
|
} from '@/utils/scheduleInvestmentPlanMapper'
|
||||||
|
import {
|
||||||
|
formatFrequencyDayLabel,
|
||||||
|
frequencyDayForApi,
|
||||||
|
isoWeekdayToCode,
|
||||||
|
WEEKLY_ISO_DAY_VALUES,
|
||||||
|
} from '@/utils/frequencyDay'
|
||||||
import {
|
import {
|
||||||
buildScheduleInvestmentCreateBody,
|
buildScheduleInvestmentCreateBody,
|
||||||
frequencyTypeToInterval,
|
frequencyTypeToInterval,
|
||||||
@@ -190,10 +195,13 @@ const endValueHint = computed(() =>
|
|||||||
)
|
)
|
||||||
|
|
||||||
const weekdayOptions = computed(() =>
|
const weekdayOptions = computed(() =>
|
||||||
WEEKDAY_CODES.map((code) => ({
|
WEEKLY_ISO_DAY_VALUES.map((iso) => {
|
||||||
value: code,
|
const code = isoWeekdayToCode(iso)
|
||||||
label: t(`recurring.weekday.${code}`),
|
return {
|
||||||
})),
|
value: iso,
|
||||||
|
label: code ? t(`recurring.weekday.${code}`) : iso,
|
||||||
|
}
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
const monthDayOptions = computed(() =>
|
const monthDayOptions = computed(() =>
|
||||||
@@ -228,7 +236,7 @@ watch(
|
|||||||
form.frequencyDay = ''
|
form.frequencyDay = ''
|
||||||
fieldError.frequencyDay = false
|
fieldError.frequencyDay = false
|
||||||
if (type === 'WEEKLY') {
|
if (type === 'WEEKLY') {
|
||||||
form.frequencyDay = 'MON'
|
form.frequencyDay = '1'
|
||||||
}
|
}
|
||||||
if (type === 'MONTHLY') {
|
if (type === 'MONTHLY') {
|
||||||
form.frequencyDay = '1'
|
form.frequencyDay = '1'
|
||||||
@@ -434,10 +442,7 @@ function intervalTextForPlan(plan: RecurringPlan) {
|
|||||||
if (plan.frequencyType) {
|
if (plan.frequencyType) {
|
||||||
let text = t(`recurring.frequency.${plan.frequencyType}`)
|
let text = t(`recurring.frequency.${plan.frequencyType}`)
|
||||||
if (plan.frequencyDay) {
|
if (plan.frequencyDay) {
|
||||||
const dayLabel =
|
const dayLabel = formatFrequencyDayLabel(plan.frequencyType, plan.frequencyDay, t)
|
||||||
plan.frequencyType === 'WEEKLY'
|
|
||||||
? t(`recurring.weekday.${plan.frequencyDay}`)
|
|
||||||
: t('recurring.monthDayOption', { day: plan.frequencyDay })
|
|
||||||
text = `${text} · ${dayLabel}`
|
text = `${text} · ${dayLabel}`
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
@@ -582,8 +587,9 @@ function buildNextExecutionPreviewBody() {
|
|||||||
frequencyType,
|
frequencyType,
|
||||||
ctOffsetMinutes: form.ctOffsetMinutes,
|
ctOffsetMinutes: form.ctOffsetMinutes,
|
||||||
}
|
}
|
||||||
if (showFrequencyDay.value && form.frequencyDay) {
|
const apiDay = frequencyDayForApi(frequencyType, form.frequencyDay)
|
||||||
body.frequencyDay = form.frequencyDay
|
if (apiDay) {
|
||||||
|
body.frequencyDay = apiDay
|
||||||
}
|
}
|
||||||
if (form.startDate) {
|
if (form.startDate) {
|
||||||
body.startDate = form.startDate
|
body.startDate = form.startDate
|
||||||
@@ -802,10 +808,7 @@ function formatDetailOrderPrice(plan: ScheduleInvestmentPlanDetailPlan) {
|
|||||||
function formatDetailFrequency(plan: ScheduleInvestmentPlanDetailPlan) {
|
function formatDetailFrequency(plan: ScheduleInvestmentPlanDetailPlan) {
|
||||||
let text = t(`recurring.frequency.${plan.frequencyType}`)
|
let text = t(`recurring.frequency.${plan.frequencyType}`)
|
||||||
if (plan.frequencyDay) {
|
if (plan.frequencyDay) {
|
||||||
const dayLabel =
|
const dayLabel = formatFrequencyDayLabel(plan.frequencyType, plan.frequencyDay, t)
|
||||||
plan.frequencyType === 'WEEKLY'
|
|
||||||
? t(`recurring.weekday.${plan.frequencyDay}`)
|
|
||||||
: t('recurring.monthDayOption', { day: plan.frequencyDay })
|
|
||||||
text = `${text} · ${dayLabel}`
|
text = `${text} · ${dayLabel}`
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
@@ -955,10 +958,7 @@ const previewInterval = computed(() => {
|
|||||||
: 'MONTHLY'
|
: 'MONTHLY'
|
||||||
let text = t(`recurring.frequency.${freqKey}`)
|
let text = t(`recurring.frequency.${freqKey}`)
|
||||||
if (draft.value.frequencyDay) {
|
if (draft.value.frequencyDay) {
|
||||||
const dayLabel =
|
const dayLabel = formatFrequencyDayLabel(freqKey, draft.value.frequencyDay, t)
|
||||||
freqKey === 'WEEKLY'
|
|
||||||
? t(`recurring.weekday.${draft.value.frequencyDay}`)
|
|
||||||
: t('recurring.monthDayOption', { day: draft.value.frequencyDay })
|
|
||||||
text = `${text} · ${dayLabel}`
|
text = `${text} · ${dayLabel}`
|
||||||
}
|
}
|
||||||
return text
|
return text
|
||||||
|
|||||||
Reference in New Issue
Block a user