diff --git a/IMPACT_ANALYSIS.md b/IMPACT_ANALYSIS.md index b27329d..0bbcc1b 100644 --- a/IMPACT_ANALYSIS.md +++ b/IMPACT_ANALYSIS.md @@ -896,3 +896,124 @@ ### 5. 验证与测试 - 填妥交易所与周期后点「预览下次执行」:按钮 loading、弹窗内 loading,成功后显示日期与时间;接口失败时弹窗关闭且已有错误提示。 + +--- + +## 补充:创建计划 productCode 入参(2026-10-12) + +### 1. 改动概览 + +- **背景与目标**:创建定投计划(及 CT 时间选项)应提交接口字段 `productCode`(如 `1`),不得使用展示用 `productCodeUnique`(如 `1.HK`)。 +- **涉及模块**:`InvestAsset`、`mapSimpleStockToAsset`、`buildScheduleInvestmentCreateBody`、`resolvePickedProductCode`、`scheduleInvestmentPlanMapper`。 +- **改动类型**:缺陷修复。 + +### 2. 方法级改动分析 + +| 位置 | 差异 | +|------|------| +| `InvestAsset` | 新增 `productCode`(接口用),与 `productCodeUnique`(展示用)区分。 | +| `mapSimpleStockToAsset` | 从 `getSimpleStockPage` 写入 `productCode`;`code` 仍为 `productCodeUnique` 优先。 | +| `buildScheduleInvestmentCreateBody` | `productCode` 仅取自 `asset.productCode`。 | +| `resolvePickedProductCode` | CT 选项请求与创建计划一致,仅用 `asset.productCode`。 | + +### 3. 调用方与影响范围 + +- 创建计划、`options/ct-time`;**破坏性变更**:否(修正错误入参)。 + +### 4. 风险与回滚 + +- **风险级别**:低;**回滚方式是否简单**:是。 + +### 5. 验证与测试 + +- 选择 SEHK `1.HK` 资产创建计划,请求体 `productCode` 为 `1`;CT 偏移选项请求同上。 + +--- + +## 补充:计划详情执行记录移除委托状态列(2026-10-12) + +### 1. 改动概览 + +- **背景与目标**:计划详情「最近执行」表格不再展示委托状态。 +- **涉及模块**:`RecurringInvestmentView.vue`、三语言 `planDetail.colOrderStatus`。 +- **改动类型**:界面调整。 + +### 2. 方法级改动分析 + +| 位置 | 差异 | +|------|------| +| `detail-exec-table` | 删除 `colOrderStatus` 表头与 `row.orderStatus` 单元格。 | + +### 3. 调用方与影响范围 + +- 仅详情弹窗执行记录表;**破坏性变更**:否(接口仍可能返回 `orderStatus`,前端不展示)。 + +### 4. 风险与回滚 + +- **风险级别**:低;**回滚方式是否简单**:是。 + +### 5. 验证与测试 + +- 打开计划详情,确认执行记录表为四列:计划执行日、实际交易日、金额/数量、执行状态。 + +--- + +## 补充:计划详情执行记录成交字段列(2026-10-13) + +### 1. 改动概览 + +- **背景与目标**:详情「最近执行」表去掉「实际交易日」,展示关联委托成交的 `filledQty`、`filledAmount`、`avgFilledPrice`。 +- **涉及模块**:`ScheduleInvestmentPlanExecutionRecord`、`RecurringInvestmentView.vue`、三语言 `planDetail` 列文案。 +- **改动类型**:界面调整 + 类型扩展。 + +### 2. 方法级改动分析 + +| 位置 | 差异 | +|------|------| +| `ScheduleInvestmentPlanExecutionRecord` | 新增可选字段 `filledQty`、`filledAmount`、`avgFilledPrice`。 | +| `detail-exec-table` | 移除 `actualTradeDate` 列;新增三列及 `formatExecutionFilledQty/Amount/AvgFilledPrice` 格式化。 | +| i18n | 删除 `colActualDate`;新增 `colFilledQty`、`colFilledAmount`、`colAvgFilledPrice`。 | + +### 3. 调用方与影响范围 + +- 仅计划详情弹窗执行记录表;数据来自 `recentExecutions`,无额外请求。**破坏性变更**:否(列展示调整,接口字段向后兼容)。 + +### 4. 风险与回滚 + +- **风险级别**:低;**回滚方式是否简单**:是。 + +### 5. 验证与测试 + +- 打开计划详情,确认列为:计划执行日、金额/数量、成交数量、成交金额、成交均价、执行状态;无成交数据时三列显示 `0` 或 `—`(字段缺失为 `—`,数值 `0` 正常格式化)。 + +--- + +## 补充:计划详情「最近执行」响应式布局(2026-10-13) + +### 1. 改动概览 + +- **背景与目标**:执行记录列增多后,加宽详情弹窗、移动端卡片化、平板横滑提示与表头首列 sticky。 +- **涉及模块**:`RecurringInvestmentView.vue`、三语言 `executionTableScrollHint`。 +- **改动类型**:界面 / 响应式。 + +### 2. 方法级改动分析 + +| 位置 | 差异 | +|------|------| +| `detailDialogWidth` | 移动 `520px`、平板 `720px`、桌面 `920px`(均 `96vw` 上限)。 | +| `detail-exec-block` | `<768px` 每条执行为卡片;否则表格 + `.app-table-scroll`。 | +| `.detail-exec-table` | 紧凑 padding、表头可换行、计划执行日列 sticky。 | + +### 3. 调用方与影响范围 + +- 仅计划详情弹窗;**破坏性变更**:否。 + +### 4. 风险与回滚 + +- **风险级别**:低;**回滚方式是否简单**:是。 + +### 5. 验证与测试 + +- 移动宽:执行记录为卡片,无横向挤压。 +- 平板 / 窄桌面:表格可横滑,见提示文案;首列固定。 +- 宽桌面:弹窗加宽后六列表格可读。 diff --git a/TASK_SUMMARY.md b/TASK_SUMMARY.md index ad6c678..f93bc66 100644 --- a/TASK_SUMMARY.md +++ b/TASK_SUMMARY.md @@ -270,3 +270,31 @@ - `next-execution` 响应改为 `nextExecutionDate`、`nextExecutionTime`;弹窗仅展示「下次执行日」「执行时间」。 - 请求期间仅按钮 `:loading`;数据返回后再打开弹窗。 - 参见 `IMPACT_ANALYSIS.md` 对应补充。 + +--- + +## 补充:创建计划使用 productCode(2026-10-12) + +- `InvestAsset` 区分 `productCode`(接口)与 `productCodeUnique`(展示);创建与 CT 选项请求提交 `productCode`(如 `1`)。 +- 参见 `IMPACT_ANALYSIS.md` 对应补充。 + +--- + +## 补充:计划详情执行记录去掉委托状态列(2026-10-12) + +- 详情弹窗「最近执行」表格移除「委托状态」列及 `colOrderStatus` 文案。 +- 参见 `IMPACT_ANALYSIS.md` 对应补充。 + +--- + +## 补充:计划详情执行记录成交三列(2026-10-13) + +- 移除「实际交易日」列;新增成交数量、成交金额、成交均价(`filledQty` / `filledAmount` / `avgFilledPrice`)。 +- 参见 `IMPACT_ANALYSIS.md` 对应补充。 + +--- + +## 补充:最近执行表响应式(2026-10-13) + +- 详情弹窗按断点加宽;移动端执行记录卡片列表,平板/桌面表格横滑 + 首列 sticky + 滑动提示。 +- 参见 `IMPACT_ANALYSIS.md` 对应补充。 diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index 1bf3fc6..14851a1 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -50,10 +50,12 @@ export default { recentExecutions: 'Recent executions', executionEmpty: 'No execution records', colScheduledDate: 'Scheduled date', - colActualDate: 'Trade date', colExecutionStatus: 'Execution', - colOrderStatus: 'Order', colAmount: 'Amount / qty', + colFilledQty: 'Filled qty', + colFilledAmount: 'Filled amount', + colAvgFilledPrice: 'Avg fill price', + executionTableScrollHint: 'Swipe horizontally to see all columns', missingPlanNo: 'Plan number is missing', }, createTitle: 'Create recurring investment', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 10ca1ac..bcda2de 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -50,10 +50,12 @@ export default { recentExecutions: '最近执行', executionEmpty: '暂无执行记录', colScheduledDate: '计划执行日', - colActualDate: '实际交易日', colExecutionStatus: '执行状态', - colOrderStatus: '委托状态', colAmount: '金额/数量', + colFilledQty: '成交数量', + colFilledAmount: '成交金额', + colAvgFilledPrice: '成交均价', + executionTableScrollHint: '表格可左右滑动查看全部字段', missingPlanNo: '缺少计划编号,无法查询详情', }, createTitle: '创建定期投资', diff --git a/src/i18n/locales/zh-TW.ts b/src/i18n/locales/zh-TW.ts index 502f960..401e2b6 100644 --- a/src/i18n/locales/zh-TW.ts +++ b/src/i18n/locales/zh-TW.ts @@ -50,10 +50,12 @@ export default { recentExecutions: '最近執行', executionEmpty: '暫無執行記錄', colScheduledDate: '計劃執行日', - colActualDate: '實際交易日', colExecutionStatus: '執行狀態', - colOrderStatus: '委託狀態', colAmount: '金額/數量', + colFilledQty: '成交數量', + colFilledAmount: '成交金額', + colAvgFilledPrice: '成交均價', + executionTableScrollHint: '表格可左右滑動查看全部欄位', missingPlanNo: '缺少計劃編號,無法查詢詳情', }, createTitle: '創建定期投資', diff --git a/src/types/recurringInvestment.ts b/src/types/recurringInvestment.ts index efef19e..d67bafd 100644 --- a/src/types/recurringInvestment.ts +++ b/src/types/recurringInvestment.ts @@ -24,6 +24,9 @@ export interface InvestAsset { /** 对接产品接口时的主键,如 SZMK-2728 */ productKey?: string exchangeCode?: string + /** 下单/接口用产品代号,如 1 */ + productCode?: string + /** 界面展示用交易所唯一码,如 1.HK */ productCodeUnique?: string } diff --git a/src/types/scheduleInvestment.ts b/src/types/scheduleInvestment.ts index dc431eb..97436df 100644 --- a/src/types/scheduleInvestment.ts +++ b/src/types/scheduleInvestment.ts @@ -189,6 +189,12 @@ export interface ScheduleInvestmentPlanExecutionRecord { limitPrice?: string | number | null orderStatus?: string executionStatus?: string + /** Filled quantity from linked order */ + filledQty?: string | number | null + /** Filled amount from linked order */ + filledAmount?: string | number | null + /** Average filled price = filledAmount / filledQty when filledQty > 0 */ + avgFilledPrice?: string | number | null failureCode?: string failureMessage?: string } diff --git a/src/utils/scheduleInvestmentPayload.ts b/src/utils/scheduleInvestmentPayload.ts index 1373b20..6d02eef 100644 --- a/src/utils/scheduleInvestmentPayload.ts +++ b/src/utils/scheduleInvestmentPayload.ts @@ -43,7 +43,7 @@ export function buildScheduleInvestmentCreateBody( input: ScheduleFormPayloadInput, ): ScheduleInvestmentCreateBody { const endType = resolveEndType(input) - const productCode = input.asset.productCodeUnique || input.asset.code + const productCode = input.asset.productCode?.trim() ?? '' const body: ScheduleInvestmentCreateBody = { accountId: input.accountId ?? '', diff --git a/src/utils/scheduleInvestmentPlanMapper.ts b/src/utils/scheduleInvestmentPlanMapper.ts index 04b185f..d6be900 100644 --- a/src/utils/scheduleInvestmentPlanMapper.ts +++ b/src/utils/scheduleInvestmentPlanMapper.ts @@ -81,6 +81,7 @@ export function mapSchedulePlanRecordToRecurringPlan( engName: record.productEngName?.trim() ?? '', localName: record.productLocalName?.trim() ?? '', exchangeCode: record.exchangeCode, + productCode: code, productCodeUnique: record.productCode, } const amount = diff --git a/src/views/RecurringInvestmentView.vue b/src/views/RecurringInvestmentView.vue index c46722e..80eebcd 100644 --- a/src/views/RecurringInvestmentView.vue +++ b/src/views/RecurringInvestmentView.vue @@ -83,7 +83,14 @@ interface FormState { const { t, locale } = useI18n() const { si18n } = useServiceI18n() -const { isMobile } = useBreakpoint() +const { isMobile, isTablet, isDesktop } = useBreakpoint() + +const detailDialogWidth = computed(() => { + if (isMobile.value) return 'min(520px, 96vw)' + if (isDesktop.value) return 'min(920px, 96vw)' + if (isTablet.value) return 'min(720px, 96vw)' + return 'min(720px, 96vw)' +}) const plansStore = useRecurringPlansStore() const authStore = useAuthStore() @@ -314,7 +321,7 @@ function resolvePickedProductCode(): string { if (!asset) { return '' } - return asset.productCodeUnique?.trim() || asset.code.trim() + return asset.productCode?.trim() ?? '' } watch( @@ -345,16 +352,18 @@ function mapSimpleStockToAsset(item: SimpleStockItem): InvestAsset { const type: AssetType = item.productType === '1' ? 'stock' : 'etf' const engName = item.engName?.trim() ?? '' const localName = item.localName?.trim() ?? '' + const productCode = item.productCode?.trim() || '' const productCodeUnique = item.productCodeUnique?.trim() || '' return { - code: productCodeUnique || item.productCode, - name: localName || engName || productCodeUnique || item.productCode, + code: productCodeUnique || productCode, + name: localName || engName || productCodeUnique || productCode, engName, localName, type, productKey: item.productKey, exchangeCode: item.exchangeCode, - productCodeUnique: productCodeUnique || item.productCode, + productCode, + productCodeUnique: productCodeUnique || productCode, } } @@ -942,6 +951,27 @@ function formatExecutionAmount(row: ScheduleInvestmentPlanExecutionRecord) { return amount > 0 ? formatHkd(amount) : '—' } +function formatExecutionFilledQty(row: ScheduleInvestmentPlanExecutionRecord) { + if (row.filledQty === null || row.filledQty === undefined || row.filledQty === '') { + return '—' + } + return t('recurring.previewQuantity', { n: parsePlanDecimal(row.filledQty) }) +} + +function formatExecutionFilledAmount(row: ScheduleInvestmentPlanExecutionRecord) { + if (row.filledAmount === null || row.filledAmount === undefined || row.filledAmount === '') { + return '—' + } + return formatHkd(parsePlanDecimal(row.filledAmount)) +} + +function formatExecutionAvgFilledPrice(row: ScheduleInvestmentPlanExecutionRecord) { + if (row.avgFilledPrice === null || row.avgFilledPrice === undefined || row.avgFilledPrice === '') { + return '—' + } + return formatHkd(parsePlanDecimal(row.avgFilledPrice)) +} + function closePlanDetail() { detailVisible.value = false } @@ -1537,9 +1567,9 @@ const previewOffset = computed(() =>
@@ -1573,32 +1603,78 @@ const previewOffset = computed(() =>