Update environment configuration and enhance asset loading:
- Change development API proxy target to `https://192.168.3.233` and set base URL for recurring investment. - Introduce `.env.production` for production environment with similar settings. - Modify `loadExchangeAssets` to support pagination and scrolling for asset loading, improving user experience. - Update related documentation and internationalization files for new features.
This commit is contained in:
+3
-2
@@ -1,3 +1,4 @@
|
|||||||
# 开发环境 API:留空则请求同源路径,由 vite proxy 转发到 VITE_API_PROXY_TARGET
|
# 开发环境 API:留空则请求同源路径,由 vite proxy 转发到 VITE_API_PROXY_TARGET
|
||||||
VITE_API_BASE_URL=
|
VITE_API_BASE_URL=/api
|
||||||
VITE_API_PROXY_TARGET=http://192.168.3.230:18000
|
VITE_API_PROXY_TARGET=https://192.168.3.233
|
||||||
|
VITE_BASE_URL=/recurring-investment
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
VITE_API_BASE_URL=/api
|
||||||
|
VITE_BASE_URL=/recurring-investment
|
||||||
+103
-4
@@ -52,7 +52,7 @@
|
|||||||
|
|
||||||
### 1. 改动概览
|
### 1. 改动概览
|
||||||
|
|
||||||
- **背景与目标**:与 Order-H5 一致,支持 URL `token` 写入请求头;本地开发代理到 `192.168.3.230:18000`;通过 `getAllList` 加载服务端多语言。
|
- **背景与目标**:与 Order-H5 一致,支持 URL `token` 写入请求头;本地开发代理到 `192.168.3.233`;通过 `getAllList` 加载服务端多语言。
|
||||||
- **涉及模块**:`vite.config.ts`、`src/api/http.ts`、`src/stores/auth.ts`、`src/stores/serviceI18n.ts`、`src/api/base.ts`、`src/api/scheduleInvestment.ts`、`src/main.ts`、`src/App.vue`。
|
- **涉及模块**:`vite.config.ts`、`src/api/http.ts`、`src/stores/auth.ts`、`src/stores/serviceI18n.ts`、`src/api/base.ts`、`src/api/scheduleInvestment.ts`、`src/main.ts`、`src/App.vue`。
|
||||||
- **改动类型**:配置变更 + 基础设施。
|
- **改动类型**:配置变更 + 基础设施。
|
||||||
|
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
|
|
||||||
| 位置 | 差异 |
|
| 位置 | 差异 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| `vite.config.ts` | `/base`、`/trade` 代理至 `VITE_API_PROXY_TARGET`(默认 `http://192.168.3.230:18000`)。 |
|
| `vite.config.ts` | `/base`、`/trade` 代理至 `VITE_API_PROXY_TARGET`(默认 `https://192.168.3.233`)。 |
|
||||||
| `.env.development` | `VITE_API_BASE_URL` 置空;新增 `VITE_API_PROXY_TARGET`。 |
|
| `.env.development` | `VITE_API_BASE_URL` 置空;新增 `VITE_API_PROXY_TARGET`。 |
|
||||||
| `src/api/http.ts` | 请求头 `token`、`Accept-Language`;统一 `code === '0'` 成功处理。 |
|
| `src/api/http.ts` | 请求头 `token`、`Accept-Language`;统一 `code === '0'` 成功处理。 |
|
||||||
| `src/utils/token.ts` / `stores/auth.ts` | URL 优先解析 token 并持久化(localStorage)。 |
|
| `src/utils/token.ts` / `stores/auth.ts` | URL 优先解析 token 并持久化(localStorage)。 |
|
||||||
@@ -89,7 +89,7 @@
|
|||||||
|
|
||||||
| 配置项 | 含义 | Dev 默认 |
|
| 配置项 | 含义 | Dev 默认 |
|
||||||
|--------|------|----------|
|
|--------|------|----------|
|
||||||
| `VITE_API_PROXY_TARGET` | Vite 代理目标主机 | `http://192.168.3.230:18000` |
|
| `VITE_API_PROXY_TARGET` | Vite 代理目标主机 | `https://192.168.3.233` |
|
||||||
| `VITE_API_BASE_URL` | Axios baseURL,空则同源 | 空 |
|
| `VITE_API_BASE_URL` | Axios baseURL,空则同源 | 空 |
|
||||||
|
|
||||||
---
|
---
|
||||||
@@ -146,7 +146,7 @@
|
|||||||
|
|
||||||
### 1. 改动概览
|
### 1. 改动概览
|
||||||
|
|
||||||
- **背景与目标**:创建表单在选定交易所后,从 `POST /product/product/getSimpleStockPage` 拉取可投标的,请求体 `exchangeCodes` 为交易所下拉选中值;支持 `searchName` 与 `isUpdatedToday: true`。
|
- **背景与目标**:创建表单在选定交易所后,从 `POST /product/product/getSimpleStockPage` 拉取可投标的,请求体 `exchangeCodes` 为交易所下拉选中值;支持 `searchName`。
|
||||||
- **涉及模块**:`src/api/product.ts`、`vite.config.ts`(`/product` 代理)、`RecurringInvestmentView.vue`、`AssetChipPicker.vue`、`InvestAsset` 类型、i18n。
|
- **涉及模块**:`src/api/product.ts`、`vite.config.ts`(`/product` 代理)、`RecurringInvestmentView.vue`、`AssetChipPicker.vue`、`InvestAsset` 类型、i18n。
|
||||||
- **改动类型**:功能新增。
|
- **改动类型**:功能新增。
|
||||||
|
|
||||||
@@ -738,3 +738,102 @@
|
|||||||
### 5. 验证与测试
|
### 5. 验证与测试
|
||||||
|
|
||||||
- `npm run build`。
|
- `npm run build`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 补充:VITE_BASE_URL 接入 Vite base(2026-10-10)
|
||||||
|
|
||||||
|
### 1. 改动概览
|
||||||
|
|
||||||
|
- **背景与目标**:`.env.development` 已配置 `VITE_BASE_URL=/recurring-investment`,需写入 Vite `base`,使静态资源与 Vue Router 子路径部署一致。
|
||||||
|
- **涉及模块**:`vite.config.ts`、`src/env.d.ts`、`.env.production`、`README.md`。
|
||||||
|
- **改动类型**:配置变更。
|
||||||
|
|
||||||
|
### 2. 方法级改动分析
|
||||||
|
|
||||||
|
| 位置 | 差异 |
|
||||||
|
|------|------|
|
||||||
|
| `vite.config.ts` | `loadEnv` 读取 `VITE_BASE_URL`,经 `resolveViteBase` 设置 `base`(子路径末尾补 `/`)。 |
|
||||||
|
| `.env.production` | 新增,与开发环境一致的 `VITE_BASE_URL` / `VITE_API_BASE_URL`。 |
|
||||||
|
| `src/router/index.ts` | 无改;已使用 `createWebHistory(import.meta.env.BASE_URL)`。 |
|
||||||
|
|
||||||
|
### 3. 调用方与影响范围
|
||||||
|
|
||||||
|
- 开发与生产入口 URL 为 `/recurring-investment/`(非根路径 `/`)。
|
||||||
|
- 网关/Nginx 需将应用挂载到同子路径或做等价转发。
|
||||||
|
- **破坏性变更**:是(若此前按根路径 `/` 访问或部署,需改为带子路径)。
|
||||||
|
|
||||||
|
### 4. 风险与回滚
|
||||||
|
|
||||||
|
- **风险级别**:低。
|
||||||
|
- **回滚**:删除或清空 `VITE_BASE_URL`,移除 `vite.config.ts` 的 `base` 绑定。
|
||||||
|
- **回滚方式是否简单**:是。
|
||||||
|
|
||||||
|
### 5. 验证与测试
|
||||||
|
|
||||||
|
- `npm run build`;本地 `npm run dev` 访问 `http://localhost:5173/recurring-investment/`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 补充:仅创建计划请求体携带 clientId / accountId(2026-10-10)
|
||||||
|
|
||||||
|
### 1. 改动概览
|
||||||
|
|
||||||
|
- **背景与目标**:除「新建定投计划」外,其余交易/产品/基础接口不应在请求体中附带 URL 解析的 `clientId`、`accountId`。
|
||||||
|
- **涉及模块**:`src/api/http.ts`。
|
||||||
|
- **改动类型**:缺陷修复。
|
||||||
|
|
||||||
|
### 2. 方法级改动分析
|
||||||
|
|
||||||
|
| 位置 | 差异 |
|
||||||
|
|------|------|
|
||||||
|
| `src/api/http.ts` | 移除请求拦截器对 POST `data` 的全局合并 `clientId`/`accountId`。 |
|
||||||
|
| `src/utils/scheduleInvestmentPayload.ts` / `RecurringInvestmentView.vue` | 无改;创建计划仍通过 `buildScheduleInvestmentCreateBody` 显式写入两字段。 |
|
||||||
|
|
||||||
|
### 3. 调用方与影响范围
|
||||||
|
|
||||||
|
- 经 `http` 的列表、详情、终止、CT 选项、下次执行预览、`getSimpleStockPage`、`getAllList` 等不再自动附带账户上下文。
|
||||||
|
- **破坏性变更**:否(与后端契约对齐;创建接口行为不变)。
|
||||||
|
|
||||||
|
### 4. 风险与回滚
|
||||||
|
|
||||||
|
- **风险级别**:低。
|
||||||
|
- **回滚**:恢复 `http.ts` 拦截器中的合并逻辑。
|
||||||
|
- **回滚方式是否简单**:是。
|
||||||
|
|
||||||
|
### 5. 验证与测试
|
||||||
|
|
||||||
|
- `npm run build`;创建计划请求体仍含 `clientId`/`accountId`;其它 POST 仅含业务字段。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 补充:资产下拉分页与滚动加载(2026-10-10)
|
||||||
|
|
||||||
|
### 1. 改动概览
|
||||||
|
|
||||||
|
- **背景与目标**:创建表单资产搜索不再单次请求 `size: 100000`;改为每页 50 条,下拉滚动触底追加加载。
|
||||||
|
- **涉及模块**:`RecurringInvestmentView.vue`、`AssetChipPicker.vue`、i18n。
|
||||||
|
- **改动类型**:性能优化 / 体验改进。
|
||||||
|
|
||||||
|
### 2. 方法级改动分析
|
||||||
|
|
||||||
|
| 位置 | 差异 |
|
||||||
|
|------|------|
|
||||||
|
| `RecurringInvestmentView.vue` | `loadExchangeAssets` 支持 `current` 递增与 `append`;维护 `assetHasMore` / `assetPage` / `assetSearchKeyword`;`onAssetLoadMore` 处理触底。 |
|
||||||
|
| `AssetChipPicker.vue` | `.asset-drop` 滚动监听,触发 `loadMore`;展示 `hasMore` / `loadingMore` 底部提示。 |
|
||||||
|
| i18n | 新增 `recurring.assetScrollHint`。 |
|
||||||
|
|
||||||
|
### 3. 调用方与影响范围
|
||||||
|
|
||||||
|
- 仅定期投资创建表单资产晶片;搜索防抖仍传 `searchName` 并重置为第一页。
|
||||||
|
- **破坏性变更**:否。
|
||||||
|
|
||||||
|
### 4. 风险与回滚
|
||||||
|
|
||||||
|
- **风险级别**:低;若后端 `current` 为 1 起始需与现用 0 起始对齐(与原实现一致)。
|
||||||
|
- **回滚**:恢复单次大 `size` 请求并移除 `loadMore` 链路。
|
||||||
|
- **回滚方式是否简单**:是。
|
||||||
|
|
||||||
|
### 5. 验证与测试
|
||||||
|
|
||||||
|
- `npm run build`;交易所切换、搜索、下拉滚动加载更多。
|
||||||
|
|||||||
@@ -14,7 +14,7 @@ npm install
|
|||||||
npm run dev
|
npm run dev
|
||||||
```
|
```
|
||||||
|
|
||||||
默认开发地址:`http://localhost:5173/`。`vite.config.ts` 已设置 `server.host: true`,启动后终端会打印 **Network** 地址,同网段设备可用本机 IP 访问(如 `http://192.168.x.x:5173/`);经内网穿透或自定义域名访问时依赖 `allowedHosts: true`。
|
默认开发地址:`http://localhost:5173/recurring-investment/`(由 `.env.development` 的 `VITE_BASE_URL` 决定,对应 Vite `base` 与 Vue Router `history`)。`vite.config.ts` 已设置 `server.host: true`,启动后终端会打印 **Network** 地址,同网段设备可用本机 IP 访问(如 `http://192.168.x.x:5173/recurring-investment/`);经内网穿透或自定义域名访问时依赖 `allowedHosts: true`。
|
||||||
|
|
||||||
未带查询参数时,语言为繁体(`lang=tw`)、主题为深色(`theme=dark`)。
|
未带查询参数时,语言为繁体(`lang=tw`)、主题为深色(`theme=dark`)。
|
||||||
|
|
||||||
@@ -29,7 +29,8 @@ npm run preview # 预览生产构建
|
|||||||
|
|
||||||
| 文件 | 变量 | 说明 |
|
| 文件 | 变量 | 说明 |
|
||||||
|------|------|------|
|
|------|------|------|
|
||||||
| `.env.development` | `VITE_API_BASE_URL` | 接口基础路径,默认 `/api` |
|
| `.env.development` / `.env.production` | `VITE_API_BASE_URL` | 接口基础路径,默认 `/api` |
|
||||||
|
| 同上 | `VITE_BASE_URL` | 应用部署子路径,默认 `/recurring-investment`(写入 Vite `base`) |
|
||||||
|
|
||||||
## URL 查询参数
|
## URL 查询参数
|
||||||
|
|
||||||
|
|||||||
+24
-2
@@ -32,7 +32,7 @@
|
|||||||
|
|
||||||
## 补充:getSimpleStockPage 资产搜索
|
## 补充:getSimpleStockPage 资产搜索
|
||||||
|
|
||||||
- 交易所下拉选中后请求标的;`exchangeCodes: [exchangeCode]`,`isUpdatedToday: true`。
|
- 交易所下拉选中后请求标的;`exchangeCodes: [exchangeCode]`。
|
||||||
- 晶片搜索框防抖调用同一接口并传 `searchName`。
|
- 晶片搜索框防抖调用同一接口并传 `searchName`。
|
||||||
- 映射字段含 `productKey`、`productCodeUnique` 等,便于后续创建计划入参。
|
- 映射字段含 `productKey`、`productCodeUnique` 等,便于后续创建计划入参。
|
||||||
|
|
||||||
@@ -54,7 +54,7 @@
|
|||||||
## 补充:Token、代理与服务端多语言
|
## 补充:Token、代理与服务端多语言
|
||||||
|
|
||||||
- URL 参数 `token` 写入请求头 `token`(参考 Order-H5);路由跳转保留 query(含 token)。
|
- URL 参数 `token` 写入请求头 `token`(参考 Order-H5);路由跳转保留 query(含 token)。
|
||||||
- 开发代理:`/base/*` → `getAllList` 等;`/trade/*` → `schedule-investment/exchanges` 等(目标 `192.168.3.230:18000`,可改 `.env.development`)。
|
- 开发代理:`/base/*` → `getAllList` 等;`/trade/*` → `schedule-investment/exchanges` 等(目标 `192.168.3.233`,可改 `.env.development`)。
|
||||||
- 有 token 时启动加载 `getSystemI18nAllList`;组件内用 `useServiceI18n().si18n('I18N_ID')` 解析服务端文案。
|
- 有 token 时启动加载 `getSystemI18nAllList`;组件内用 `useServiceI18n().si18n('I18N_ID')` 解析服务端文案。
|
||||||
- `getScheduleInvestmentExchanges()` 已封装,待业务页面对接。
|
- `getScheduleInvestmentExchanges()` 已封装,待业务页面对接。
|
||||||
- `npm run build` 通过。
|
- `npm run build` 通过。
|
||||||
@@ -233,3 +233,25 @@
|
|||||||
- 新增 `src/utils/frequencyDay.ts`,将接口 `1`/`MONDAY`/`MON` 等 weekly 值与带前导零的 monthly 值规范后用于列表与详情展示。
|
- 新增 `src/utils/frequencyDay.ts`,将接口 `1`/`MONDAY`/`MON` 等 weekly 值与带前导零的 monthly 值规范后用于列表与详情展示。
|
||||||
- 创建/预览提交:`WEEKLY` 为 `1`–`7`,`MONTHLY` 为 `1`–`31`;`DAILY` 不传 `frequencyDay`。界面仍显示星期中文名。
|
- 创建/预览提交:`WEEKLY` 为 `1`–`7`,`MONTHLY` 为 `1`–`31`;`DAILY` 不传 `frequencyDay`。界面仍显示星期中文名。
|
||||||
- 验证:`npm run build`;参见 `IMPACT_ANALYSIS.md` 对应补充。
|
- 验证:`npm run build`;参见 `IMPACT_ANALYSIS.md` 对应补充。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 补充:VITE_BASE_URL 子路径(2026-10-10)
|
||||||
|
|
||||||
|
- `vite.config.ts` 将 `VITE_BASE_URL` 映射为 Vite `base`;新增 `.env.production`。
|
||||||
|
- 开发/预览入口:`/recurring-investment/`。
|
||||||
|
- 验证:`npm run build`。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 补充:HTTP 不再全局注入 clientId / accountId(2026-10-10)
|
||||||
|
|
||||||
|
- 移除 `src/api/http.ts` 对全部 POST 请求体的自动合并;仅 `POST /trade/schedule-investment/plan/create` 在 `buildScheduleInvestmentCreateBody` 中携带 URL 的 `clientId`、`accountId`。
|
||||||
|
- 验证:`npm run build`;参见 `IMPACT_ANALYSIS.md` 对应补充。
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 补充:资产下拉滚动分页(2026-10-10)
|
||||||
|
|
||||||
|
- `getSimpleStockPage` 每页 50 条;下拉触底 `loadMore` 追加,搜索仍防抖并重置页码。
|
||||||
|
- 验证:`npm run build`;参见 `IMPACT_ANALYSIS.md` 对应补充。
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
import axios from 'axios'
|
import axios from 'axios'
|
||||||
import { ElMessage } from 'element-plus'
|
import { ElMessage } from 'element-plus'
|
||||||
import { useAppStore } from '@/stores/app'
|
import { useAppStore } from '@/stores/app'
|
||||||
import { useAuthStore } from '@/stores/auth'
|
|
||||||
import { getToken, removeToken } from '@/utils/token'
|
import { getToken, removeToken } from '@/utils/token'
|
||||||
|
|
||||||
function resolveBaseURL(): string {
|
function resolveBaseURL(): string {
|
||||||
@@ -32,28 +31,6 @@ http.interceptors.request.use(
|
|||||||
} catch {
|
} catch {
|
||||||
config.headers['Accept-Language'] = 'zh-TW'
|
config.headers['Accept-Language'] = 'zh-TW'
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
const authStore = useAuthStore()
|
|
||||||
const { clientId, accountId } = authStore
|
|
||||||
if (clientId || accountId) {
|
|
||||||
const base =
|
|
||||||
config.data !== undefined &&
|
|
||||||
typeof config.data === 'object' &&
|
|
||||||
config.data !== null &&
|
|
||||||
!(config.data instanceof FormData)
|
|
||||||
? { ...(config.data as Record<string, unknown>) }
|
|
||||||
: {}
|
|
||||||
if (clientId) {
|
|
||||||
base.clientId = clientId
|
|
||||||
}
|
|
||||||
if (accountId) {
|
|
||||||
base.accountId = accountId
|
|
||||||
}
|
|
||||||
config.data = base
|
|
||||||
}
|
|
||||||
} catch {
|
|
||||||
// pinia 未就绪时跳过
|
|
||||||
}
|
|
||||||
return config
|
return config
|
||||||
},
|
},
|
||||||
(error) => Promise.reject(error),
|
(error) => Promise.reject(error),
|
||||||
|
|||||||
+1
-1
@@ -20,7 +20,7 @@ export interface SimpleStockPageRequest {
|
|||||||
size: number
|
size: number
|
||||||
searchName: string
|
searchName: string
|
||||||
exchangeCodes: string[]
|
exchangeCodes: string[]
|
||||||
isUpdatedToday: boolean
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface SimpleStockPageData {
|
export interface SimpleStockPageData {
|
||||||
|
|||||||
@@ -15,11 +15,14 @@ const props = defineProps<{
|
|||||||
invalid?: boolean
|
invalid?: boolean
|
||||||
disabled?: boolean
|
disabled?: boolean
|
||||||
loading?: boolean
|
loading?: boolean
|
||||||
|
loadingMore?: boolean
|
||||||
|
hasMore?: boolean
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const emit = defineEmits<{
|
const emit = defineEmits<{
|
||||||
'update:modelValue': [value: InvestAsset[]]
|
'update:modelValue': [value: InvestAsset[]]
|
||||||
search: [keyword: string]
|
search: [keyword: string]
|
||||||
|
loadMore: []
|
||||||
}>()
|
}>()
|
||||||
|
|
||||||
const { t, locale } = useI18n()
|
const { t, locale } = useI18n()
|
||||||
@@ -30,8 +33,22 @@ function assetDisplayName(asset: InvestAsset) {
|
|||||||
const query = ref('')
|
const query = ref('')
|
||||||
const open = ref(false)
|
const open = ref(false)
|
||||||
const wrapRef = ref<HTMLElement | null>(null)
|
const wrapRef = ref<HTMLElement | null>(null)
|
||||||
|
const dropRef = ref<HTMLElement | null>(null)
|
||||||
const inputRef = ref<HTMLInputElement | null>(null)
|
const inputRef = ref<HTMLInputElement | null>(null)
|
||||||
|
|
||||||
|
const SCROLL_LOAD_THRESHOLD_PX = 48
|
||||||
|
|
||||||
|
function onDropScroll() {
|
||||||
|
const el = dropRef.value
|
||||||
|
if (!el || !props.hasMore || props.loading || props.loadingMore) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
const distanceToBottom = el.scrollHeight - el.scrollTop - el.clientHeight
|
||||||
|
if (distanceToBottom <= SCROLL_LOAD_THRESHOLD_PX) {
|
||||||
|
emit('loadMore')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
const pickedCodes = computed(
|
const pickedCodes = computed(
|
||||||
() => new Set(props.modelValue.map((item) => investAssetOptionKey(item))),
|
() => new Set(props.modelValue.map((item) => investAssetOptionKey(item))),
|
||||||
)
|
)
|
||||||
@@ -159,7 +176,13 @@ onBeforeUnmount(() => {
|
|||||||
@keydown="onKeydown"
|
@keydown="onKeydown"
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
<div v-show="open" class="asset-drop" role="listbox">
|
<div
|
||||||
|
v-show="open"
|
||||||
|
ref="dropRef"
|
||||||
|
class="asset-drop"
|
||||||
|
role="listbox"
|
||||||
|
@scroll.passive="onDropScroll"
|
||||||
|
>
|
||||||
<button
|
<button
|
||||||
v-for="asset in filtered"
|
v-for="asset in filtered"
|
||||||
:key="investAssetOptionKey(asset)"
|
:key="investAssetOptionKey(asset)"
|
||||||
@@ -173,6 +196,12 @@ onBeforeUnmount(() => {
|
|||||||
</button>
|
</button>
|
||||||
<div v-if="loading" class="asset-none">{{ t('recurring.assetLoading') }}</div>
|
<div v-if="loading" class="asset-none">{{ t('recurring.assetLoading') }}</div>
|
||||||
<div v-else-if="!filtered.length" class="asset-none">{{ t('recurring.assetEmpty') }}</div>
|
<div v-else-if="!filtered.length" class="asset-none">{{ t('recurring.assetEmpty') }}</div>
|
||||||
|
<div v-else-if="loadingMore" class="asset-none asset-none--footer">
|
||||||
|
{{ t('recurring.assetLoading') }}
|
||||||
|
</div>
|
||||||
|
<div v-else-if="hasMore" class="asset-none asset-none--footer">
|
||||||
|
{{ t('recurring.assetScrollHint') }}
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
@@ -346,5 +375,10 @@ onBeforeUnmount(() => {
|
|||||||
color: var(--color-muted);
|
color: var(--color-muted);
|
||||||
font-size: 12.5px;
|
font-size: 12.5px;
|
||||||
text-align: center;
|
text-align: center;
|
||||||
|
|
||||||
|
&--footer {
|
||||||
|
padding: 8px 12px;
|
||||||
|
font-size: 12px;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
Vendored
+1
@@ -3,6 +3,7 @@
|
|||||||
interface ImportMetaEnv {
|
interface ImportMetaEnv {
|
||||||
readonly VITE_API_BASE_URL: string
|
readonly VITE_API_BASE_URL: string
|
||||||
readonly VITE_API_PROXY_TARGET?: string
|
readonly VITE_API_PROXY_TARGET?: string
|
||||||
|
readonly VITE_BASE_URL?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
interface ImportMeta {
|
interface ImportMeta {
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export default {
|
|||||||
assetSupport: 'Supported: stocks, ETFs',
|
assetSupport: 'Supported: stocks, ETFs',
|
||||||
assetEmpty: 'No matching assets',
|
assetEmpty: 'No matching assets',
|
||||||
assetLoading: 'Loading…',
|
assetLoading: 'Loading…',
|
||||||
|
assetScrollHint: 'Scroll for more',
|
||||||
removeAsset: 'Remove {code}',
|
removeAsset: 'Remove {code}',
|
||||||
startDate: 'Start date',
|
startDate: 'Start date',
|
||||||
amount: 'Amount',
|
amount: 'Amount',
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export default {
|
|||||||
assetSupport: '支持的: 股票、ETF',
|
assetSupport: '支持的: 股票、ETF',
|
||||||
assetEmpty: '没有匹配的资产',
|
assetEmpty: '没有匹配的资产',
|
||||||
assetLoading: '加载中…',
|
assetLoading: '加载中…',
|
||||||
|
assetScrollHint: '继续滚动加载更多',
|
||||||
removeAsset: '移除 {code}',
|
removeAsset: '移除 {code}',
|
||||||
startDate: '开始日期',
|
startDate: '开始日期',
|
||||||
amount: '金额',
|
amount: '金额',
|
||||||
|
|||||||
@@ -64,6 +64,7 @@ export default {
|
|||||||
assetSupport: '支持的: 股票、ETF',
|
assetSupport: '支持的: 股票、ETF',
|
||||||
assetEmpty: '沒有匹配的資產',
|
assetEmpty: '沒有匹配的資產',
|
||||||
assetLoading: '載入中…',
|
assetLoading: '載入中…',
|
||||||
|
assetScrollHint: '繼續捲動載入更多',
|
||||||
removeAsset: '移除 {code}',
|
removeAsset: '移除 {code}',
|
||||||
startDate: '開始日期',
|
startDate: '開始日期',
|
||||||
amount: '金額',
|
amount: '金額',
|
||||||
|
|||||||
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']
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ import {
|
|||||||
import { formatHkd, parseAmountInput } from '@/utils/formatMoney'
|
import { formatHkd, parseAmountInput } from '@/utils/formatMoney'
|
||||||
import {
|
import {
|
||||||
investAssetDisplayName,
|
investAssetDisplayName,
|
||||||
|
investAssetOptionKey,
|
||||||
resolveAppLocale,
|
resolveAppLocale,
|
||||||
scheduleProductLineDisplay,
|
scheduleProductLineDisplay,
|
||||||
} from '@/utils/investAssetDisplay'
|
} from '@/utils/investAssetDisplay'
|
||||||
@@ -90,6 +91,12 @@ const exchangeOptions = ref<ScheduleInvestmentExchangeItem[]>([])
|
|||||||
const exchangeLoading = ref(false)
|
const exchangeLoading = ref(false)
|
||||||
const exchangeAssets = ref<InvestAsset[]>([])
|
const exchangeAssets = ref<InvestAsset[]>([])
|
||||||
const assetLoading = ref(false)
|
const assetLoading = ref(false)
|
||||||
|
const assetLoadingMore = ref(false)
|
||||||
|
const assetHasMore = ref(false)
|
||||||
|
const assetSearchKeyword = ref('')
|
||||||
|
const assetPage = ref(0)
|
||||||
|
/** `getSimpleStockPage` 分页大小(滚动触底加载更多) */
|
||||||
|
const ASSET_PAGE_SIZE = 50
|
||||||
let assetSearchTimer: ReturnType<typeof setTimeout> | undefined
|
let assetSearchTimer: ReturnType<typeof setTimeout> | undefined
|
||||||
const picked = ref<InvestAsset[]>([])
|
const picked = ref<InvestAsset[]>([])
|
||||||
const assetInvalid = ref(false)
|
const assetInvalid = ref(false)
|
||||||
@@ -351,21 +358,78 @@ function mapSimpleStockToAsset(item: SimpleStockItem): InvestAsset {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
async function loadExchangeAssets(exchangeCode: string, searchName = '') {
|
function mergeExchangeAssets(existing: InvestAsset[], incoming: InvestAsset[]): InvestAsset[] {
|
||||||
|
const seen = new Set(existing.map((item) => investAssetOptionKey(item)))
|
||||||
|
const next = existing.slice()
|
||||||
|
for (const item of incoming) {
|
||||||
|
const key = investAssetOptionKey(item)
|
||||||
|
if (!seen.has(key)) {
|
||||||
|
seen.add(key)
|
||||||
|
next.push(item)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return next
|
||||||
|
}
|
||||||
|
|
||||||
|
function resolveAssetHasMore(
|
||||||
|
loadedCount: number,
|
||||||
|
pageRecordsLength: number,
|
||||||
|
total: number | undefined,
|
||||||
|
): boolean {
|
||||||
|
if (total != null && !Number.isNaN(total)) {
|
||||||
|
return loadedCount < total
|
||||||
|
}
|
||||||
|
return pageRecordsLength >= ASSET_PAGE_SIZE
|
||||||
|
}
|
||||||
|
|
||||||
|
async function loadExchangeAssets(
|
||||||
|
exchangeCode: string,
|
||||||
|
searchName = '',
|
||||||
|
options?: { append?: boolean },
|
||||||
|
) {
|
||||||
|
const append = options?.append ?? false
|
||||||
|
if (append) {
|
||||||
|
if (assetLoadingMore.value || assetLoading.value || !assetHasMore.value) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
assetLoadingMore.value = true
|
||||||
|
} else {
|
||||||
assetLoading.value = true
|
assetLoading.value = true
|
||||||
|
assetPage.value = 0
|
||||||
|
assetSearchKeyword.value = searchName
|
||||||
|
assetHasMore.value = false
|
||||||
|
}
|
||||||
|
|
||||||
|
const page = append ? assetPage.value + 1 : 0
|
||||||
|
const effectiveSearch = append ? assetSearchKeyword.value : searchName
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const res = await getSimpleStockPage({
|
const res = await getSimpleStockPage({
|
||||||
current: 0,
|
current: page,
|
||||||
size: 100000,
|
size: ASSET_PAGE_SIZE,
|
||||||
searchName,
|
searchName: effectiveSearch,
|
||||||
exchangeCodes: [exchangeCode],
|
exchangeCodes: [exchangeCode],
|
||||||
isUpdatedToday: true,
|
|
||||||
})
|
})
|
||||||
exchangeAssets.value = extractSimpleStockRecords(res.data).map(mapSimpleStockToAsset)
|
const raw = res.data
|
||||||
|
const records = extractSimpleStockRecords(raw).map(mapSimpleStockToAsset)
|
||||||
|
const total = raw && !Array.isArray(raw) && raw.total != null ? raw.total : undefined
|
||||||
|
|
||||||
|
if (append) {
|
||||||
|
exchangeAssets.value = mergeExchangeAssets(exchangeAssets.value, records)
|
||||||
|
assetPage.value = page
|
||||||
|
} else {
|
||||||
|
exchangeAssets.value = records
|
||||||
|
assetPage.value = 0
|
||||||
|
}
|
||||||
|
assetHasMore.value = resolveAssetHasMore(exchangeAssets.value.length, records.length, total)
|
||||||
} catch {
|
} catch {
|
||||||
|
if (!append) {
|
||||||
exchangeAssets.value = []
|
exchangeAssets.value = []
|
||||||
|
}
|
||||||
|
assetHasMore.value = false
|
||||||
} finally {
|
} finally {
|
||||||
assetLoading.value = false
|
assetLoading.value = false
|
||||||
|
assetLoadingMore.value = false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -381,6 +445,13 @@ function onAssetSearch(keyword: string) {
|
|||||||
}, 300)
|
}, 300)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function onAssetLoadMore() {
|
||||||
|
if (!form.exchangeCode) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
loadExchangeAssets(form.exchangeCode, assetSearchKeyword.value, { append: true })
|
||||||
|
}
|
||||||
|
|
||||||
watch(
|
watch(
|
||||||
() => form.exchangeCode,
|
() => form.exchangeCode,
|
||||||
(code) => {
|
(code) => {
|
||||||
@@ -388,6 +459,9 @@ watch(
|
|||||||
assetInvalid.value = false
|
assetInvalid.value = false
|
||||||
if (!code) {
|
if (!code) {
|
||||||
exchangeAssets.value = []
|
exchangeAssets.value = []
|
||||||
|
assetHasMore.value = false
|
||||||
|
assetSearchKeyword.value = ''
|
||||||
|
assetPage.value = 0
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
loadExchangeAssets(code)
|
loadExchangeAssets(code)
|
||||||
@@ -1157,7 +1231,10 @@ const previewOffset = computed(() =>
|
|||||||
:invalid="assetInvalid"
|
:invalid="assetInvalid"
|
||||||
:disabled="!form.exchangeCode"
|
:disabled="!form.exchangeCode"
|
||||||
:loading="assetLoading"
|
:loading="assetLoading"
|
||||||
|
:loading-more="assetLoadingMore"
|
||||||
|
:has-more="assetHasMore"
|
||||||
@search="onAssetSearch"
|
@search="onAssetSearch"
|
||||||
|
@load-more="onAssetLoadMore"
|
||||||
/>
|
/>
|
||||||
<div class="hint">{{ t('recurring.assetSupport') }}</div>
|
<div class="hint">{{ t('recurring.assetSupport') }}</div>
|
||||||
</div>
|
</div>
|
||||||
|
|||||||
+26
-16
@@ -5,14 +5,24 @@ import Components from 'unplugin-vue-components/vite'
|
|||||||
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
import { ElementPlusResolver } from 'unplugin-vue-components/resolvers'
|
||||||
import { defineConfig, loadEnv } from 'vite'
|
import { defineConfig, loadEnv } from 'vite'
|
||||||
|
|
||||||
const DEFAULT_PROXY_TARGET = 'http://192.168.3.230:18000'
|
const DEFAULT_PROXY_TARGET = 'https://192.168.3.233'
|
||||||
|
|
||||||
|
/** Vite `base`:与 `VITE_BASE_URL` 对齐,供 `import.meta.env.BASE_URL` 与 Vue Router 使用 */
|
||||||
|
function resolveViteBase(raw: string | undefined): string {
|
||||||
|
const trimmed = (raw ?? '/').trim()
|
||||||
|
if (!trimmed || trimmed === '/') return '/'
|
||||||
|
const withLeading = trimmed.startsWith('/') ? trimmed : `/${trimmed}`
|
||||||
|
return withLeading.endsWith('/') ? withLeading : `${withLeading}/`
|
||||||
|
}
|
||||||
|
|
||||||
// https://vite.dev/config/
|
// https://vite.dev/config/
|
||||||
export default defineConfig(({ mode }) => {
|
export default defineConfig(({ mode }) => {
|
||||||
const env = loadEnv(mode, process.cwd(), '')
|
const env = loadEnv(mode, process.cwd(), '')
|
||||||
const proxyTarget = (env.VITE_API_PROXY_TARGET || DEFAULT_PROXY_TARGET).trim().replace(/\/+$/, '')
|
const proxyTarget = (env.VITE_API_PROXY_TARGET || DEFAULT_PROXY_TARGET).trim().replace(/\/+$/, '')
|
||||||
|
const base = resolveViteBase(env.VITE_BASE_URL)
|
||||||
|
|
||||||
return {
|
return {
|
||||||
|
base,
|
||||||
plugins: [
|
plugins: [
|
||||||
vue(),
|
vue(),
|
||||||
AutoImport({
|
AutoImport({
|
||||||
@@ -35,21 +45,21 @@ export default defineConfig(({ mode }) => {
|
|||||||
open: true,
|
open: true,
|
||||||
allowedHosts: true,
|
allowedHosts: true,
|
||||||
proxy: {
|
proxy: {
|
||||||
'/base': {
|
// '/base': {
|
||||||
target: proxyTarget,
|
// target: proxyTarget,
|
||||||
changeOrigin: true,
|
// changeOrigin: true,
|
||||||
secure: false,
|
// secure: false,
|
||||||
},
|
// },
|
||||||
'/trade': {
|
// '/trade': {
|
||||||
target: proxyTarget,
|
// target: proxyTarget,
|
||||||
changeOrigin: true,
|
// changeOrigin: true,
|
||||||
secure: false,
|
// secure: false,
|
||||||
},
|
// },
|
||||||
'/product': {
|
// '/product': {
|
||||||
target: proxyTarget,
|
// target: proxyTarget,
|
||||||
changeOrigin: true,
|
// changeOrigin: true,
|
||||||
secure: false,
|
// secure: false,
|
||||||
},
|
// },
|
||||||
'/api': {
|
'/api': {
|
||||||
target: proxyTarget,
|
target: proxyTarget,
|
||||||
changeOrigin: true,
|
changeOrigin: true,
|
||||||
|
|||||||
Reference in New Issue
Block a user