diff --git a/.env.development b/.env.development index 08b8a3d..175fd21 100644 --- a/.env.development +++ b/.env.development @@ -1,3 +1,4 @@ # 开发环境 API:留空则请求同源路径,由 vite proxy 转发到 VITE_API_PROXY_TARGET -VITE_API_BASE_URL= -VITE_API_PROXY_TARGET=http://192.168.3.230:18000 +VITE_API_BASE_URL=/api +VITE_API_PROXY_TARGET=https://192.168.3.233 +VITE_BASE_URL=/recurring-investment \ No newline at end of file diff --git a/.env.production b/.env.production new file mode 100644 index 0000000..dbd2691 --- /dev/null +++ b/.env.production @@ -0,0 +1,2 @@ +VITE_API_BASE_URL=/api +VITE_BASE_URL=/recurring-investment diff --git a/IMPACT_ANALYSIS.md b/IMPACT_ANALYSIS.md index 4d504c8..9b4290b 100644 --- a/IMPACT_ANALYSIS.md +++ b/IMPACT_ANALYSIS.md @@ -52,7 +52,7 @@ ### 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`。 - **改动类型**:配置变更 + 基础设施。 @@ -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`。 | | `src/api/http.ts` | 请求头 `token`、`Accept-Language`;统一 `code === '0'` 成功处理。 | | `src/utils/token.ts` / `stores/auth.ts` | URL 优先解析 token 并持久化(localStorage)。 | @@ -89,7 +89,7 @@ | 配置项 | 含义 | 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,空则同源 | 空 | --- @@ -146,7 +146,7 @@ ### 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。 - **改动类型**:功能新增。 @@ -738,3 +738,102 @@ ### 5. 验证与测试 - `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`;交易所切换、搜索、下拉滚动加载更多。 diff --git a/README.md b/README.md index 76a49ec..9fb5024 100644 --- a/README.md +++ b/README.md @@ -14,7 +14,7 @@ npm install 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`)。 @@ -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 查询参数 diff --git a/TASK_SUMMARY.md b/TASK_SUMMARY.md index 032fd19..86c66c3 100644 --- a/TASK_SUMMARY.md +++ b/TASK_SUMMARY.md @@ -32,7 +32,7 @@ ## 补充:getSimpleStockPage 资产搜索 -- 交易所下拉选中后请求标的;`exchangeCodes: [exchangeCode]`,`isUpdatedToday: true`。 +- 交易所下拉选中后请求标的;`exchangeCodes: [exchangeCode]`。 - 晶片搜索框防抖调用同一接口并传 `searchName`。 - 映射字段含 `productKey`、`productCodeUnique` 等,便于后续创建计划入参。 @@ -54,7 +54,7 @@ ## 补充: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')` 解析服务端文案。 - `getScheduleInvestmentExchanges()` 已封装,待业务页面对接。 - `npm run build` 通过。 @@ -233,3 +233,25 @@ - 新增 `src/utils/frequencyDay.ts`,将接口 `1`/`MONDAY`/`MON` 等 weekly 值与带前导零的 monthly 值规范后用于列表与详情展示。 - 创建/预览提交:`WEEKLY` 为 `1`–`7`,`MONTHLY` 为 `1`–`31`;`DAILY` 不传 `frequencyDay`。界面仍显示星期中文名。 - 验证:`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` 对应补充。 diff --git a/src/api/http.ts b/src/api/http.ts index ed71e2c..01bada7 100644 --- a/src/api/http.ts +++ b/src/api/http.ts @@ -1,7 +1,6 @@ import axios from 'axios' import { ElMessage } from 'element-plus' import { useAppStore } from '@/stores/app' -import { useAuthStore } from '@/stores/auth' import { getToken, removeToken } from '@/utils/token' function resolveBaseURL(): string { @@ -32,28 +31,6 @@ http.interceptors.request.use( } catch { 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) } - : {} - if (clientId) { - base.clientId = clientId - } - if (accountId) { - base.accountId = accountId - } - config.data = base - } - } catch { - // pinia 未就绪时跳过 - } return config }, (error) => Promise.reject(error), diff --git a/src/api/product.ts b/src/api/product.ts index 217ae3d..be9854f 100644 --- a/src/api/product.ts +++ b/src/api/product.ts @@ -20,7 +20,7 @@ export interface SimpleStockPageRequest { size: number searchName: string exchangeCodes: string[] - isUpdatedToday: boolean + } export interface SimpleStockPageData { diff --git a/src/components/recurring/AssetChipPicker.vue b/src/components/recurring/AssetChipPicker.vue index 0c57f7b..bf8b8a2 100644 --- a/src/components/recurring/AssetChipPicker.vue +++ b/src/components/recurring/AssetChipPicker.vue @@ -15,11 +15,14 @@ const props = defineProps<{ invalid?: boolean disabled?: boolean loading?: boolean + loadingMore?: boolean + hasMore?: boolean }>() const emit = defineEmits<{ 'update:modelValue': [value: InvestAsset[]] search: [keyword: string] + loadMore: [] }>() const { t, locale } = useI18n() @@ -30,8 +33,22 @@ function assetDisplayName(asset: InvestAsset) { const query = ref('') const open = ref(false) const wrapRef = ref(null) +const dropRef = ref(null) const inputRef = ref(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( () => new Set(props.modelValue.map((item) => investAssetOptionKey(item))), ) @@ -159,7 +176,13 @@ onBeforeUnmount(() => { @keydown="onKeydown" /> -
+
{{ t('recurring.assetLoading') }}
{{ t('recurring.assetEmpty') }}
+ +
@@ -346,5 +375,10 @@ onBeforeUnmount(() => { color: var(--color-muted); font-size: 12.5px; text-align: center; + + &--footer { + padding: 8px 12px; + font-size: 12px; + } } diff --git a/src/env.d.ts b/src/env.d.ts index 8851d27..6eab9ca 100644 --- a/src/env.d.ts +++ b/src/env.d.ts @@ -3,6 +3,7 @@ interface ImportMetaEnv { readonly VITE_API_BASE_URL: string readonly VITE_API_PROXY_TARGET?: string + readonly VITE_BASE_URL?: string } interface ImportMeta { diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index 53e50d3..1bf3fc6 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -64,6 +64,7 @@ export default { assetSupport: 'Supported: stocks, ETFs', assetEmpty: 'No matching assets', assetLoading: 'Loading…', + assetScrollHint: 'Scroll for more', removeAsset: 'Remove {code}', startDate: 'Start date', amount: 'Amount', diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 9481830..10ca1ac 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -64,6 +64,7 @@ export default { assetSupport: '支持的: 股票、ETF', assetEmpty: '没有匹配的资产', assetLoading: '加载中…', + assetScrollHint: '继续滚动加载更多', removeAsset: '移除 {code}', startDate: '开始日期', amount: '金额', diff --git a/src/i18n/locales/zh-TW.ts b/src/i18n/locales/zh-TW.ts index ef9722f..502f960 100644 --- a/src/i18n/locales/zh-TW.ts +++ b/src/i18n/locales/zh-TW.ts @@ -64,6 +64,7 @@ export default { assetSupport: '支持的: 股票、ETF', assetEmpty: '沒有匹配的資產', assetLoading: '載入中…', + assetScrollHint: '繼續捲動載入更多', removeAsset: '移除 {code}', startDate: '開始日期', amount: '金額', diff --git a/src/types/components.d.ts b/src/types/components.d.ts index 511c031..ca4834e 100644 --- a/src/types/components.d.ts +++ b/src/types/components.d.ts @@ -20,7 +20,6 @@ declare module 'vue' { ElDatePicker: typeof import('element-plus/es')['ElDatePicker'] ElDialog: typeof import('element-plus/es')['ElDialog'] ElInput: typeof import('element-plus/es')['ElInput'] - ElInputNumber: typeof import('element-plus/es')['ElInputNumber'] ElOption: typeof import('element-plus/es')['ElOption'] ElRow: typeof import('element-plus/es')['ElRow'] ElSelect: typeof import('element-plus/es')['ElSelect'] diff --git a/src/views/RecurringInvestmentView.vue b/src/views/RecurringInvestmentView.vue index 90272b8..7d51881 100644 --- a/src/views/RecurringInvestmentView.vue +++ b/src/views/RecurringInvestmentView.vue @@ -45,6 +45,7 @@ import { import { formatHkd, parseAmountInput } from '@/utils/formatMoney' import { investAssetDisplayName, + investAssetOptionKey, resolveAppLocale, scheduleProductLineDisplay, } from '@/utils/investAssetDisplay' @@ -90,6 +91,12 @@ const exchangeOptions = ref([]) const exchangeLoading = ref(false) const exchangeAssets = ref([]) 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 | undefined const picked = ref([]) const assetInvalid = ref(false) @@ -351,21 +358,78 @@ function mapSimpleStockToAsset(item: SimpleStockItem): InvestAsset { } } -async function loadExchangeAssets(exchangeCode: string, searchName = '') { - assetLoading.value = true +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 + assetPage.value = 0 + assetSearchKeyword.value = searchName + assetHasMore.value = false + } + + const page = append ? assetPage.value + 1 : 0 + const effectiveSearch = append ? assetSearchKeyword.value : searchName + try { const res = await getSimpleStockPage({ - current: 0, - size: 100000, - searchName, + current: page, + size: ASSET_PAGE_SIZE, + searchName: effectiveSearch, 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 { - exchangeAssets.value = [] + if (!append) { + exchangeAssets.value = [] + } + assetHasMore.value = false } finally { assetLoading.value = false + assetLoadingMore.value = false } } @@ -381,6 +445,13 @@ function onAssetSearch(keyword: string) { }, 300) } +function onAssetLoadMore() { + if (!form.exchangeCode) { + return + } + loadExchangeAssets(form.exchangeCode, assetSearchKeyword.value, { append: true }) +} + watch( () => form.exchangeCode, (code) => { @@ -388,6 +459,9 @@ watch( assetInvalid.value = false if (!code) { exchangeAssets.value = [] + assetHasMore.value = false + assetSearchKeyword.value = '' + assetPage.value = 0 return } loadExchangeAssets(code) @@ -1157,7 +1231,10 @@ const previewOffset = computed(() => :invalid="assetInvalid" :disabled="!form.exchangeCode" :loading="assetLoading" + :loading-more="assetLoadingMore" + :has-more="assetHasMore" @search="onAssetSearch" + @load-more="onAssetLoadMore" />
{{ t('recurring.assetSupport') }}
diff --git a/vite.config.ts b/vite.config.ts index 5052f0d..cb133f7 100644 --- a/vite.config.ts +++ b/vite.config.ts @@ -5,14 +5,24 @@ import Components from 'unplugin-vue-components/vite' import { ElementPlusResolver } from 'unplugin-vue-components/resolvers' 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/ export default defineConfig(({ mode }) => { const env = loadEnv(mode, process.cwd(), '') const proxyTarget = (env.VITE_API_PROXY_TARGET || DEFAULT_PROXY_TARGET).trim().replace(/\/+$/, '') + const base = resolveViteBase(env.VITE_BASE_URL) return { + base, plugins: [ vue(), AutoImport({ @@ -35,21 +45,21 @@ export default defineConfig(({ mode }) => { open: true, allowedHosts: true, proxy: { - '/base': { - target: proxyTarget, - changeOrigin: true, - secure: false, - }, - '/trade': { - target: proxyTarget, - changeOrigin: true, - secure: false, - }, - '/product': { - target: proxyTarget, - changeOrigin: true, - secure: false, - }, + // '/base': { + // target: proxyTarget, + // changeOrigin: true, + // secure: false, + // }, + // '/trade': { + // target: proxyTarget, + // changeOrigin: true, + // secure: false, + // }, + // '/product': { + // target: proxyTarget, + // changeOrigin: true, + // secure: false, + // }, '/api': { target: proxyTarget, changeOrigin: true,