Enhance recurring investment plan details and API integration:
- Introduce `productCode` for API submissions, differentiating it from `productCodeUnique`. - Remove `actualTradeDate` and `orderStatus` from the execution records table, adding `filledQty`, `filledAmount`, and `avgFilledPrice` instead. - Implement responsive design adjustments for the execution records display across devices. - Update internationalization files to reflect new column names and hints for table navigation. - Document changes in `IMPACT_ANALYSIS.md` and `TASK_SUMMARY.md` for clarity on the updates.
This commit is contained in:
@@ -896,3 +896,124 @@
|
|||||||
### 5. 验证与测试
|
### 5. 验证与测试
|
||||||
|
|
||||||
- 填妥交易所与周期后点「预览下次执行」:按钮 loading、弹窗内 loading,成功后显示日期与时间;接口失败时弹窗关闭且已有错误提示。
|
- 填妥交易所与周期后点「预览下次执行」:按钮 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. 验证与测试
|
||||||
|
|
||||||
|
- 移动宽:执行记录为卡片,无横向挤压。
|
||||||
|
- 平板 / 窄桌面:表格可横滑,见提示文案;首列固定。
|
||||||
|
- 宽桌面:弹窗加宽后六列表格可读。
|
||||||
|
|||||||
@@ -270,3 +270,31 @@
|
|||||||
- `next-execution` 响应改为 `nextExecutionDate`、`nextExecutionTime`;弹窗仅展示「下次执行日」「执行时间」。
|
- `next-execution` 响应改为 `nextExecutionDate`、`nextExecutionTime`;弹窗仅展示「下次执行日」「执行时间」。
|
||||||
- 请求期间仅按钮 `:loading`;数据返回后再打开弹窗。
|
- 请求期间仅按钮 `:loading`;数据返回后再打开弹窗。
|
||||||
- 参见 `IMPACT_ANALYSIS.md` 对应补充。
|
- 参见 `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` 对应补充。
|
||||||
|
|||||||
@@ -50,10 +50,12 @@ export default {
|
|||||||
recentExecutions: 'Recent executions',
|
recentExecutions: 'Recent executions',
|
||||||
executionEmpty: 'No execution records',
|
executionEmpty: 'No execution records',
|
||||||
colScheduledDate: 'Scheduled date',
|
colScheduledDate: 'Scheduled date',
|
||||||
colActualDate: 'Trade date',
|
|
||||||
colExecutionStatus: 'Execution',
|
colExecutionStatus: 'Execution',
|
||||||
colOrderStatus: 'Order',
|
|
||||||
colAmount: 'Amount / qty',
|
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',
|
missingPlanNo: 'Plan number is missing',
|
||||||
},
|
},
|
||||||
createTitle: 'Create recurring investment',
|
createTitle: 'Create recurring investment',
|
||||||
|
|||||||
@@ -50,10 +50,12 @@ export default {
|
|||||||
recentExecutions: '最近执行',
|
recentExecutions: '最近执行',
|
||||||
executionEmpty: '暂无执行记录',
|
executionEmpty: '暂无执行记录',
|
||||||
colScheduledDate: '计划执行日',
|
colScheduledDate: '计划执行日',
|
||||||
colActualDate: '实际交易日',
|
|
||||||
colExecutionStatus: '执行状态',
|
colExecutionStatus: '执行状态',
|
||||||
colOrderStatus: '委托状态',
|
|
||||||
colAmount: '金额/数量',
|
colAmount: '金额/数量',
|
||||||
|
colFilledQty: '成交数量',
|
||||||
|
colFilledAmount: '成交金额',
|
||||||
|
colAvgFilledPrice: '成交均价',
|
||||||
|
executionTableScrollHint: '表格可左右滑动查看全部字段',
|
||||||
missingPlanNo: '缺少计划编号,无法查询详情',
|
missingPlanNo: '缺少计划编号,无法查询详情',
|
||||||
},
|
},
|
||||||
createTitle: '创建定期投资',
|
createTitle: '创建定期投资',
|
||||||
|
|||||||
@@ -50,10 +50,12 @@ export default {
|
|||||||
recentExecutions: '最近執行',
|
recentExecutions: '最近執行',
|
||||||
executionEmpty: '暫無執行記錄',
|
executionEmpty: '暫無執行記錄',
|
||||||
colScheduledDate: '計劃執行日',
|
colScheduledDate: '計劃執行日',
|
||||||
colActualDate: '實際交易日',
|
|
||||||
colExecutionStatus: '執行狀態',
|
colExecutionStatus: '執行狀態',
|
||||||
colOrderStatus: '委託狀態',
|
|
||||||
colAmount: '金額/數量',
|
colAmount: '金額/數量',
|
||||||
|
colFilledQty: '成交數量',
|
||||||
|
colFilledAmount: '成交金額',
|
||||||
|
colAvgFilledPrice: '成交均價',
|
||||||
|
executionTableScrollHint: '表格可左右滑動查看全部欄位',
|
||||||
missingPlanNo: '缺少計劃編號,無法查詢詳情',
|
missingPlanNo: '缺少計劃編號,無法查詢詳情',
|
||||||
},
|
},
|
||||||
createTitle: '創建定期投資',
|
createTitle: '創建定期投資',
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ export interface InvestAsset {
|
|||||||
/** 对接产品接口时的主键,如 SZMK-2728 */
|
/** 对接产品接口时的主键,如 SZMK-2728 */
|
||||||
productKey?: string
|
productKey?: string
|
||||||
exchangeCode?: string
|
exchangeCode?: string
|
||||||
|
/** 下单/接口用产品代号,如 1 */
|
||||||
|
productCode?: string
|
||||||
|
/** 界面展示用交易所唯一码,如 1.HK */
|
||||||
productCodeUnique?: string
|
productCodeUnique?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -189,6 +189,12 @@ export interface ScheduleInvestmentPlanExecutionRecord {
|
|||||||
limitPrice?: string | number | null
|
limitPrice?: string | number | null
|
||||||
orderStatus?: string
|
orderStatus?: string
|
||||||
executionStatus?: 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
|
failureCode?: string
|
||||||
failureMessage?: string
|
failureMessage?: string
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,7 +43,7 @@ export function buildScheduleInvestmentCreateBody(
|
|||||||
input: ScheduleFormPayloadInput,
|
input: ScheduleFormPayloadInput,
|
||||||
): ScheduleInvestmentCreateBody {
|
): ScheduleInvestmentCreateBody {
|
||||||
const endType = resolveEndType(input)
|
const endType = resolveEndType(input)
|
||||||
const productCode = input.asset.productCodeUnique || input.asset.code
|
const productCode = input.asset.productCode?.trim() ?? ''
|
||||||
|
|
||||||
const body: ScheduleInvestmentCreateBody = {
|
const body: ScheduleInvestmentCreateBody = {
|
||||||
accountId: input.accountId ?? '',
|
accountId: input.accountId ?? '',
|
||||||
|
|||||||
@@ -81,6 +81,7 @@ export function mapSchedulePlanRecordToRecurringPlan(
|
|||||||
engName: record.productEngName?.trim() ?? '',
|
engName: record.productEngName?.trim() ?? '',
|
||||||
localName: record.productLocalName?.trim() ?? '',
|
localName: record.productLocalName?.trim() ?? '',
|
||||||
exchangeCode: record.exchangeCode,
|
exchangeCode: record.exchangeCode,
|
||||||
|
productCode: code,
|
||||||
productCodeUnique: record.productCode,
|
productCodeUnique: record.productCode,
|
||||||
}
|
}
|
||||||
const amount =
|
const amount =
|
||||||
|
|||||||
@@ -83,7 +83,14 @@ interface FormState {
|
|||||||
|
|
||||||
const { t, locale } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
const { si18n } = useServiceI18n()
|
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 plansStore = useRecurringPlansStore()
|
||||||
const authStore = useAuthStore()
|
const authStore = useAuthStore()
|
||||||
|
|
||||||
@@ -314,7 +321,7 @@ function resolvePickedProductCode(): string {
|
|||||||
if (!asset) {
|
if (!asset) {
|
||||||
return ''
|
return ''
|
||||||
}
|
}
|
||||||
return asset.productCodeUnique?.trim() || asset.code.trim()
|
return asset.productCode?.trim() ?? ''
|
||||||
}
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
@@ -345,16 +352,18 @@ function mapSimpleStockToAsset(item: SimpleStockItem): InvestAsset {
|
|||||||
const type: AssetType = item.productType === '1' ? 'stock' : 'etf'
|
const type: AssetType = item.productType === '1' ? 'stock' : 'etf'
|
||||||
const engName = item.engName?.trim() ?? ''
|
const engName = item.engName?.trim() ?? ''
|
||||||
const localName = item.localName?.trim() ?? ''
|
const localName = item.localName?.trim() ?? ''
|
||||||
|
const productCode = item.productCode?.trim() || ''
|
||||||
const productCodeUnique = item.productCodeUnique?.trim() || ''
|
const productCodeUnique = item.productCodeUnique?.trim() || ''
|
||||||
return {
|
return {
|
||||||
code: productCodeUnique || item.productCode,
|
code: productCodeUnique || productCode,
|
||||||
name: localName || engName || productCodeUnique || item.productCode,
|
name: localName || engName || productCodeUnique || productCode,
|
||||||
engName,
|
engName,
|
||||||
localName,
|
localName,
|
||||||
type,
|
type,
|
||||||
productKey: item.productKey,
|
productKey: item.productKey,
|
||||||
exchangeCode: item.exchangeCode,
|
exchangeCode: item.exchangeCode,
|
||||||
productCodeUnique: productCodeUnique || item.productCode,
|
productCode,
|
||||||
|
productCodeUnique: productCodeUnique || productCode,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -942,6 +951,27 @@ function formatExecutionAmount(row: ScheduleInvestmentPlanExecutionRecord) {
|
|||||||
return amount > 0 ? formatHkd(amount) : '—'
|
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() {
|
function closePlanDetail() {
|
||||||
detailVisible.value = false
|
detailVisible.value = false
|
||||||
}
|
}
|
||||||
@@ -1537,9 +1567,9 @@ const previewOffset = computed(() =>
|
|||||||
<el-dialog
|
<el-dialog
|
||||||
v-model="detailVisible"
|
v-model="detailVisible"
|
||||||
:title="t('recurring.planDetail.title')"
|
:title="t('recurring.planDetail.title')"
|
||||||
width="min(520px, 92vw)"
|
:width="detailDialogWidth"
|
||||||
align-center
|
align-center
|
||||||
class="ri-dialog"
|
class="ri-dialog ri-dialog--plan-detail"
|
||||||
@closed="onDetailDialogClosed"
|
@closed="onDetailDialogClosed"
|
||||||
>
|
>
|
||||||
<div v-loading="detailLoading">
|
<div v-loading="detailLoading">
|
||||||
@@ -1573,32 +1603,78 @@ const previewOffset = computed(() =>
|
|||||||
</dl>
|
</dl>
|
||||||
|
|
||||||
<template v-if="detailPlan && !detailLoading">
|
<template v-if="detailPlan && !detailLoading">
|
||||||
<h3 class="detail-exec-title">{{ t('recurring.planDetail.recentExecutions') }}</h3>
|
<section class="detail-exec-block" aria-labelledby="detail-exec-heading">
|
||||||
|
<h3 id="detail-exec-heading" class="detail-exec-title">
|
||||||
|
{{ t('recurring.planDetail.recentExecutions') }}
|
||||||
|
</h3>
|
||||||
<p v-if="!detailExecutions.length" class="detail-exec-empty">
|
<p v-if="!detailExecutions.length" class="detail-exec-empty">
|
||||||
{{ t('recurring.planDetail.executionEmpty') }}
|
{{ t('recurring.planDetail.executionEmpty') }}
|
||||||
</p>
|
</p>
|
||||||
<div v-else class="app-table-scroll detail-exec-scroll">
|
<div v-else-if="isMobile" class="detail-exec-cards">
|
||||||
|
<article
|
||||||
|
v-for="(row, index) in detailExecutions"
|
||||||
|
:key="row.id ?? row.executionNo ?? index"
|
||||||
|
class="detail-exec-card"
|
||||||
|
>
|
||||||
|
<div class="detail-exec-card__head">
|
||||||
|
<span class="num">{{ row.scheduledExecutionDate || '—' }}</span>
|
||||||
|
<span class="detail-exec-card__status">{{ row.executionStatus || '—' }}</span>
|
||||||
|
</div>
|
||||||
|
<dl class="detail-exec-card__meta">
|
||||||
|
<div>
|
||||||
|
<dt>{{ t('recurring.planDetail.colAmount') }}</dt>
|
||||||
|
<dd class="num">{{ formatExecutionAmount(row) }}</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>{{ t('recurring.planDetail.colFilledQty') }}</dt>
|
||||||
|
<dd class="num">{{ formatExecutionFilledQty(row) }}</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>{{ t('recurring.planDetail.colFilledAmount') }}</dt>
|
||||||
|
<dd class="num">{{ formatExecutionFilledAmount(row) }}</dd>
|
||||||
|
</div>
|
||||||
|
<div>
|
||||||
|
<dt>{{ t('recurring.planDetail.colAvgFilledPrice') }}</dt>
|
||||||
|
<dd class="num">{{ formatExecutionAvgFilledPrice(row) }}</dd>
|
||||||
|
</div>
|
||||||
|
</dl>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
<div v-else class="detail-exec-table-wrap">
|
||||||
|
<div class="app-table-scroll detail-exec-scroll">
|
||||||
<table class="plan-table detail-exec-table">
|
<table class="plan-table detail-exec-table">
|
||||||
<thead>
|
<thead>
|
||||||
<tr>
|
<tr>
|
||||||
<th class="l">{{ t('recurring.planDetail.colScheduledDate') }}</th>
|
<th class="l detail-exec-table__sticky">
|
||||||
<th class="l">{{ t('recurring.planDetail.colActualDate') }}</th>
|
{{ t('recurring.planDetail.colScheduledDate') }}
|
||||||
|
</th>
|
||||||
<th>{{ t('recurring.planDetail.colAmount') }}</th>
|
<th>{{ t('recurring.planDetail.colAmount') }}</th>
|
||||||
|
<th>{{ t('recurring.planDetail.colFilledQty') }}</th>
|
||||||
|
<th>{{ t('recurring.planDetail.colFilledAmount') }}</th>
|
||||||
|
<th>{{ t('recurring.planDetail.colAvgFilledPrice') }}</th>
|
||||||
<th class="l">{{ t('recurring.planDetail.colExecutionStatus') }}</th>
|
<th class="l">{{ t('recurring.planDetail.colExecutionStatus') }}</th>
|
||||||
<th class="l">{{ t('recurring.planDetail.colOrderStatus') }}</th>
|
|
||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr v-for="(row, index) in detailExecutions" :key="row.id ?? row.executionNo ?? index">
|
<tr
|
||||||
<td class="l num">{{ row.scheduledExecutionDate || '—' }}</td>
|
v-for="(row, index) in detailExecutions"
|
||||||
<td class="l num">{{ row.actualTradeDate || '—' }}</td>
|
:key="row.id ?? row.executionNo ?? index"
|
||||||
|
>
|
||||||
|
<td class="l num detail-exec-table__sticky">
|
||||||
|
{{ row.scheduledExecutionDate || '—' }}
|
||||||
|
</td>
|
||||||
<td class="num">{{ formatExecutionAmount(row) }}</td>
|
<td class="num">{{ formatExecutionAmount(row) }}</td>
|
||||||
|
<td class="num">{{ formatExecutionFilledQty(row) }}</td>
|
||||||
|
<td class="num">{{ formatExecutionFilledAmount(row) }}</td>
|
||||||
|
<td class="num">{{ formatExecutionAvgFilledPrice(row) }}</td>
|
||||||
<td class="l">{{ row.executionStatus || '—' }}</td>
|
<td class="l">{{ row.executionStatus || '—' }}</td>
|
||||||
<td class="l">{{ row.orderStatus || '—' }}</td>
|
|
||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</table>
|
</table>
|
||||||
</div>
|
</div>
|
||||||
|
<p class="detail-exec-scroll-hint">{{ t('recurring.planDetail.executionTableScrollHint') }}</p>
|
||||||
|
</div>
|
||||||
|
</section>
|
||||||
</template>
|
</template>
|
||||||
</div>
|
</div>
|
||||||
<template #footer>
|
<template #footer>
|
||||||
@@ -1931,8 +2007,14 @@ const previewOffset = computed(() =>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.detail-exec-block {
|
||||||
|
margin-top: 4px;
|
||||||
|
padding-top: 14px;
|
||||||
|
border-top: 1px solid var(--color-border);
|
||||||
|
}
|
||||||
|
|
||||||
.detail-exec-title {
|
.detail-exec-title {
|
||||||
margin: 18px 0 8px;
|
margin: 0 0 10px;
|
||||||
font-size: 13px;
|
font-size: 13px;
|
||||||
font-weight: 700;
|
font-weight: 700;
|
||||||
letter-spacing: 0.04em;
|
letter-spacing: 0.04em;
|
||||||
@@ -1946,12 +2028,138 @@ const previewOffset = computed(() =>
|
|||||||
color: var(--color-muted);
|
color: var(--color-muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-exec-scroll {
|
.detail-exec-cards {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
gap: 0;
|
||||||
margin: 0 -4px;
|
margin: 0 -4px;
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
.detail-exec-table {
|
.detail-exec-card {
|
||||||
min-width: 480px;
|
padding: 12px 14px;
|
||||||
|
border-bottom: 1px solid var(--color-border);
|
||||||
|
background: var(--color-surface);
|
||||||
|
|
||||||
|
&:last-child {
|
||||||
|
border-bottom: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__head {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: space-between;
|
||||||
|
gap: 10px;
|
||||||
|
margin-bottom: 10px;
|
||||||
|
font-size: 13px;
|
||||||
|
font-weight: 600;
|
||||||
|
}
|
||||||
|
|
||||||
|
&__status {
|
||||||
|
flex-shrink: 0;
|
||||||
|
max-width: 50%;
|
||||||
|
text-align: right;
|
||||||
|
font-size: 12px;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--color-text-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
&__meta {
|
||||||
|
display: grid;
|
||||||
|
grid-template-columns: 1fr 1fr;
|
||||||
|
gap: 10px 14px;
|
||||||
|
margin: 0;
|
||||||
|
|
||||||
|
dt {
|
||||||
|
color: var(--color-muted);
|
||||||
|
font-size: 11px;
|
||||||
|
text-transform: uppercase;
|
||||||
|
letter-spacing: 0.06em;
|
||||||
|
line-height: 1.3;
|
||||||
|
}
|
||||||
|
|
||||||
|
dd {
|
||||||
|
margin: 2px 0 0;
|
||||||
|
font-size: 13px;
|
||||||
|
word-break: break-word;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-exec-table-wrap {
|
||||||
|
margin: 0 -6px;
|
||||||
|
|
||||||
|
@include respond-up(md) {
|
||||||
|
margin: 0 -4px;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-exec-scroll {
|
||||||
|
border: 1px solid var(--color-border);
|
||||||
|
border-radius: 6px;
|
||||||
|
background: var(--color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-exec-scroll-hint {
|
||||||
|
display: none;
|
||||||
|
margin: 8px 4px 0;
|
||||||
|
font-size: 11px;
|
||||||
|
line-height: 1.4;
|
||||||
|
text-align: center;
|
||||||
|
color: var(--color-muted);
|
||||||
|
|
||||||
|
@include respond-between(md, lg) {
|
||||||
|
display: block;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.plan-table.detail-exec-table {
|
||||||
|
min-width: 780px;
|
||||||
|
table-layout: auto;
|
||||||
|
|
||||||
|
th,
|
||||||
|
td {
|
||||||
|
padding: 8px 10px;
|
||||||
|
}
|
||||||
|
|
||||||
|
th {
|
||||||
|
font-size: 11px;
|
||||||
|
letter-spacing: 0.05em;
|
||||||
|
line-height: 1.35;
|
||||||
|
white-space: normal;
|
||||||
|
vertical-align: bottom;
|
||||||
|
}
|
||||||
|
|
||||||
|
td {
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.detail-exec-table__sticky {
|
||||||
|
position: sticky;
|
||||||
|
left: 0;
|
||||||
|
z-index: 1;
|
||||||
|
min-width: 6.5rem;
|
||||||
|
box-shadow: 4px 0 8px -4px rgb(0 0 0 / 12%);
|
||||||
|
|
||||||
|
[data-theme='dark'] &,
|
||||||
|
html.dark & {
|
||||||
|
box-shadow: 4px 0 8px -4px rgb(0 0 0 / 35%);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
thead .detail-exec-table__sticky {
|
||||||
|
background: var(--color-surface-2);
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody .detail-exec-table__sticky {
|
||||||
|
background: var(--color-surface);
|
||||||
|
}
|
||||||
|
|
||||||
|
tbody tr:hover .detail-exec-table__sticky {
|
||||||
|
background: var(--color-surface-2);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
.form-body {
|
.form-body {
|
||||||
|
|||||||
Reference in New Issue
Block a user