minitest2.0 0.0.7 → 0.0.9

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/package.json +1 -1
  2. package/src/api/dashboard.ts +44 -0
  3. package/src/router/index.ts +18 -18
  4. package/src/stores/position-filter.ts +38 -0
  5. package/src/views/dashboard/index.vue +142 -17
  6. package/src/views/{foreign-position → foreign/position}/index.vue +9 -2
  7. package/src/views/{rmb-position → rmb/position}/index.vue +9 -2
  8. /package/src/views/{article-detail → announcement/detail}/index.vue +0 -0
  9. /package/src/views/{article-edit → announcement/edit}/index.vue +0 -0
  10. /package/src/views/{articles → announcement}/index.vue +0 -0
  11. /package/src/views/{foreign-position-create → foreign/position/create}/index.vue +0 -0
  12. /package/src/views/{foreign-position-detail → foreign/position/detail}/index.vue +0 -0
  13. /package/src/views/{settings → mine/settings}/index.vue +0 -0
  14. /package/src/views/{clearing-detail → rmb/clearing-detail}/index.vue +0 -0
  15. /package/src/views/{net-debit → rmb/net-debit}/index.vue +0 -0
  16. /package/src/views/{pbc-position → rmb/pbc-position}/index.vue +0 -0
  17. /package/src/views/{rmb-position-create → rmb/position/create}/index.vue +0 -0
  18. /package/src/views/{rmb-position-detail → rmb/position/detail}/index.vue +0 -0
  19. /package/src/views/{position-estimate → rmb/position-estimate}/index.vue +0 -0
  20. /package/src/views/{opening-reserve-estimate → rmb/position-estimate/opening-reserve}/index.vue +0 -0
  21. /package/src/views/{position-estimate-report → rmb/position-estimate/report}/index.vue +0 -0
  22. /package/src/views/{warning → rmb/warning}/index.vue +0 -0
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "minitest2.0",
3
- "version": "0.0.7",
3
+ "version": "0.0.9",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "repository": {
@@ -0,0 +1,44 @@
1
+ import { buildTamRequestPayload, tamRequest } from '@/api/tam'
2
+
3
+ export type RmbDashboardBody = {
4
+ /** 城银账户资金余额 */
5
+ availCredit?: string
6
+ /** 跨境人民币可用余额 */
7
+ cipsAvailBal?: string
8
+ /** 数字人民币可用余额 */
9
+ dcpTotalAmt?: string
10
+ /** 人行账户资金余额 */
11
+ settleAccAmt?: string
12
+ }
13
+
14
+ export type ForeignDashboardBody = {
15
+ eurDomesticAmt?: string
16
+ eurOverseasAmt?: string
17
+ hkdDomesticAmt?: string
18
+ hkdOverseasAmt?: string
19
+ jpyDomesticAmt?: string
20
+ jpyOverseasAmt?: string
21
+ usdDomesticAmt?: string
22
+ usdOverseasAmt?: string
23
+ }
24
+
25
+ const RMB_DASHBOARD_TRAN_CODE = '/TAM/0001/140000007'
26
+ const FOREIGN_DASHBOARD_TRAN_CODE = '/TAM/0001/140000008'
27
+
28
+ export function queryRmbDashboard() {
29
+ return tamRequest<Record<string, never>, RmbDashboardBody>(
30
+ buildTamRequestPayload({
31
+ tranCode: RMB_DASHBOARD_TRAN_CODE,
32
+ body: {},
33
+ }),
34
+ )
35
+ }
36
+
37
+ export function queryForeignDashboard() {
38
+ return tamRequest<Record<string, never>, ForeignDashboardBody>(
39
+ buildTamRequestPayload({
40
+ tranCode: FOREIGN_DASHBOARD_TRAN_CODE,
41
+ body: {},
42
+ }),
43
+ )
44
+ }
@@ -26,79 +26,79 @@ const router = createRouter({
26
26
  {
27
27
  path: '/rmb-position',
28
28
  name: 'RmbPosition',
29
- component: () => import('@/views/rmb-position/index.vue'),
29
+ component: () => import('@/views/rmb/position/index.vue'),
30
30
  meta: { requiresAuth: true, title: '本币预报信息查询' },
31
31
  },
32
32
  {
33
33
  path: '/rmb-position/new',
34
34
  name: 'RmbPositionCreate',
35
- component: () => import('@/views/rmb-position-create/index.vue'),
35
+ component: () => import('@/views/rmb/position/create/index.vue'),
36
36
  meta: { requiresAuth: true, title: '新增本币预报' },
37
37
  },
38
38
  {
39
39
  path: '/rmb-position/:predRefNo',
40
40
  name: 'RmbPositionDetail',
41
- component: () => import('@/views/rmb-position-detail/index.vue'),
41
+ component: () => import('@/views/rmb/position/detail/index.vue'),
42
42
  meta: { requiresAuth: true, title: '预报详情' },
43
43
  },
44
44
  {
45
45
  path: '/foreign-position',
46
46
  name: 'ForeignPosition',
47
- component: () => import('@/views/foreign-position/index.vue'),
47
+ component: () => import('@/views/foreign/position/index.vue'),
48
48
  meta: { requiresAuth: true, title: '外币预报信息查询' },
49
49
  },
50
50
  {
51
51
  path: '/foreign-position/new',
52
52
  name: 'ForeignPositionCreate',
53
- component: () => import('@/views/foreign-position-create/index.vue'),
53
+ component: () => import('@/views/foreign/position/create/index.vue'),
54
54
  meta: { requiresAuth: true, title: '新增外币预报' },
55
55
  },
56
56
  {
57
57
  path: '/foreign-position/:predRefNo',
58
58
  name: 'ForeignPositionDetail',
59
- component: () => import('@/views/foreign-position-detail/index.vue'),
59
+ component: () => import('@/views/foreign/position/detail/index.vue'),
60
60
  meta: { requiresAuth: true, title: '外币预报详情' },
61
61
  },
62
62
  {
63
63
  path: '/pbc-position',
64
64
  name: 'PbcPosition',
65
- component: () => import('@/views/pbc-position/index.vue'),
65
+ component: () => import('@/views/rmb/pbc-position/index.vue'),
66
66
  meta: { requiresAuth: true, title: '人行头寸余额查询' },
67
67
  },
68
68
  {
69
69
  path: '/position-estimate',
70
70
  name: 'PositionEstimate',
71
- component: () => import('@/views/position-estimate/index.vue'),
71
+ component: () => import('@/views/rmb/position-estimate/index.vue'),
72
72
  meta: { requiresAuth: true, title: '头寸匡算' },
73
73
  },
74
74
  {
75
75
  path: '/position-estimate/report',
76
76
  name: 'PositionEstimateReport',
77
- component: () => import('@/views/position-estimate-report/index.vue'),
77
+ component: () => import('@/views/rmb/position-estimate/report/index.vue'),
78
78
  meta: { requiresAuth: true, title: '头寸匡算报表' },
79
79
  },
80
80
  {
81
81
  path: '/position-estimate/opening-reserve',
82
82
  name: 'OpeningReserveEstimate',
83
- component: () => import('@/views/opening-reserve-estimate/index.vue'),
83
+ component: () => import('@/views/rmb/position-estimate/opening-reserve/index.vue'),
84
84
  meta: { requiresAuth: true, title: '日初备款数匡算' },
85
85
  },
86
86
  {
87
87
  path: '/net-debit',
88
88
  name: 'NetDebit',
89
- component: () => import('@/views/net-debit/index.vue'),
89
+ component: () => import('@/views/rmb/net-debit/index.vue'),
90
90
  meta: { requiresAuth: true, title: '人民币净借记可用额度管理' },
91
91
  },
92
92
  {
93
93
  path: '/clearing-detail',
94
94
  name: 'ClearingDetail',
95
- component: () => import('@/views/clearing-detail/index.vue'),
95
+ component: () => import('@/views/rmb/clearing-detail/index.vue'),
96
96
  meta: { requiresAuth: true, title: '人民币小额网银清算场次明细' },
97
97
  },
98
98
  {
99
99
  path: '/warning',
100
100
  name: 'Warning',
101
- component: () => import('@/views/warning/index.vue'),
101
+ component: () => import('@/views/rmb/warning/index.vue'),
102
102
  meta: { requiresAuth: true, title: '头寸管理平台' },
103
103
  },
104
104
  {
@@ -110,25 +110,25 @@ const router = createRouter({
110
110
  {
111
111
  path: '/articles',
112
112
  name: 'Articles',
113
- component: () => import('@/views/articles/index.vue'),
113
+ component: () => import('@/views/announcement/index.vue'),
114
114
  meta: { requiresAuth: true, showTabbar: true, title: '公告' },
115
115
  },
116
116
  {
117
117
  path: '/articles/new',
118
118
  name: 'ArticlePublish',
119
- component: () => import('@/views/article-edit/index.vue'),
119
+ component: () => import('@/views/announcement/edit/index.vue'),
120
120
  meta: { requiresAuth: true, title: '发布公告' },
121
121
  },
122
122
  {
123
123
  path: '/articles/:id',
124
124
  name: 'ArticleDetail',
125
- component: () => import('@/views/article-detail/index.vue'),
125
+ component: () => import('@/views/announcement/detail/index.vue'),
126
126
  meta: { requiresAuth: true, title: '公告详情' },
127
127
  },
128
128
  {
129
129
  path: '/articles/:id/edit',
130
130
  name: 'ArticleEdit',
131
- component: () => import('@/views/article-edit/index.vue'),
131
+ component: () => import('@/views/announcement/edit/index.vue'),
132
132
  meta: { requiresAuth: true, title: '编辑公告' },
133
133
  },
134
134
  {
@@ -140,7 +140,7 @@ const router = createRouter({
140
140
  {
141
141
  path: '/settings',
142
142
  name: 'Settings',
143
- component: () => import('@/views/settings/index.vue'),
143
+ component: () => import('@/views/mine/settings/index.vue'),
144
144
  meta: { requiresAuth: true, title: '设置' },
145
145
  },
146
146
  ],
@@ -0,0 +1,38 @@
1
+ import { defineStore } from 'pinia'
2
+
3
+ /**
4
+ * 头寸预报查询筛选区状态 Store
5
+ *
6
+ * 只保存列表页筛选条件面板的展开/收起状态,避免和应用级配置混在一起。
7
+ */
8
+ export const usePositionFilterStore = defineStore(
9
+ 'positionFilter',
10
+ () => {
11
+ /** 人民币头寸预报及查询列表筛选条件是否展开,默认收起并持久化用户选择。 */
12
+ const rmbFilterExpanded = ref(false)
13
+
14
+ /** 外币头寸预报及查询列表筛选条件是否展开,默认收起并持久化用户选择。 */
15
+ const foreignFilterExpanded = ref(false)
16
+
17
+ function setRmbFilterExpanded(value: boolean) {
18
+ rmbFilterExpanded.value = value
19
+ }
20
+
21
+ function setForeignFilterExpanded(value: boolean) {
22
+ foreignFilterExpanded.value = value
23
+ }
24
+
25
+ return {
26
+ rmbFilterExpanded,
27
+ foreignFilterExpanded,
28
+ setRmbFilterExpanded,
29
+ setForeignFilterExpanded,
30
+ }
31
+ },
32
+ {
33
+ persist: {
34
+ key: 'mini2_position_filter_settings',
35
+ pick: ['rmbFilterExpanded', 'foreignFilterExpanded'],
36
+ },
37
+ },
38
+ )
@@ -1,4 +1,6 @@
1
1
  <script setup lang="ts">
2
+ import { queryForeignDashboard, queryRmbDashboard } from '@/api/dashboard'
3
+
2
4
  defineOptions({ name: 'DashboardPage' })
3
5
 
4
6
  type DashboardMetric = {
@@ -37,12 +39,30 @@ const selectedUnitFactor = computed(
37
39
  () => unitOptions.find((unit) => unit.label === selectedUnit.value)?.factor || 1,
38
40
  )
39
41
 
40
- const cards: DashboardCard[] = [
42
+ const dashboardAmounts = reactive({
43
+ rmb: 0,
44
+ cityClearing: 0,
45
+ crossBorderRmb: 0,
46
+ digitalCurrency: 0,
47
+ usdDomestic: 0,
48
+ usdOverseas: 0,
49
+ eurDomestic: 0,
50
+ eurOverseas: 0,
51
+ hkdDomestic: 0,
52
+ hkdOverseas: 0,
53
+ jpyDomestic: 0,
54
+ jpyOverseas: 0,
55
+ })
56
+
57
+ const isDashboardLoading = ref(false)
58
+ const dashboardLoadError = ref('')
59
+
60
+ const cards = computed<DashboardCard[]>(() => [
41
61
  {
42
62
  name: '人民币',
43
63
  code: 'CNY',
44
64
  symbol: '¥',
45
- amount: 8888888.88,
65
+ amount: dashboardAmounts.rmb,
46
66
  image: dashboardImage('01-rmb-great-wall-bg.jpg'),
47
67
  accent: '#df2f38',
48
68
  position: 'center center',
@@ -51,7 +71,7 @@ const cards: DashboardCard[] = [
51
71
  name: '城银清',
52
72
  code: 'CNY',
53
73
  symbol: '¥',
54
- amount: 6888888.88,
74
+ amount: dashboardAmounts.cityClearing,
55
75
  image: dashboardImage('02-city-clearing-skyline-bg.jpg'),
56
76
  accent: '#0f4ca7',
57
77
  position: 'center center',
@@ -60,7 +80,7 @@ const cards: DashboardCard[] = [
60
80
  name: '跨境人民币',
61
81
  code: 'CNY',
62
82
  symbol: '¥',
63
- amount: 1888888.88,
83
+ amount: dashboardAmounts.crossBorderRmb,
64
84
  image: dashboardImage('03-cross-border-rmb-world-map-bg.jpg'),
65
85
  accent: '#078b95',
66
86
  position: 'center center',
@@ -69,7 +89,7 @@ const cards: DashboardCard[] = [
69
89
  name: '数字货币',
70
90
  code: 'CNY',
71
91
  symbol: '¥',
72
- amount: 888888.88,
92
+ amount: dashboardAmounts.digitalCurrency,
73
93
  image: dashboardImage('04-digital-currency-fintech-bg.jpg'),
74
94
  accent: '#4423b7',
75
95
  position: 'center center',
@@ -78,8 +98,8 @@ const cards: DashboardCard[] = [
78
98
  name: '美元',
79
99
  code: 'USD',
80
100
  metrics: [
81
- { label: '境内', symbol: '$', amount: 888888.88 },
82
- { label: '境外', symbol: '$', amount: 888888.88 },
101
+ { label: '境内', symbol: '$', amount: dashboardAmounts.usdDomestic },
102
+ { label: '境外', symbol: '$', amount: dashboardAmounts.usdOverseas },
83
103
  ],
84
104
  image: dashboardImage('05-usd-liberty-bg.jpg'),
85
105
  accent: '#0d8b3f',
@@ -89,8 +109,8 @@ const cards: DashboardCard[] = [
89
109
  name: '欧元',
90
110
  code: 'EUR',
91
111
  metrics: [
92
- { label: '境内', symbol: '€', amount: 888888.88 },
93
- { label: '境外', symbol: '€', amount: 888888.88 },
112
+ { label: '境内', symbol: '€', amount: dashboardAmounts.eurDomestic },
113
+ { label: '境外', symbol: '€', amount: dashboardAmounts.eurOverseas },
94
114
  ],
95
115
  image: dashboardImage('06-eur-europe-architecture-bg.jpg'),
96
116
  accent: '#1267d5',
@@ -100,8 +120,8 @@ const cards: DashboardCard[] = [
100
120
  name: '港元',
101
121
  code: 'HKD',
102
122
  metrics: [
103
- { label: '境内', symbol: 'HK$', amount: 888888.88 },
104
- { label: '境外', symbol: 'HK$', amount: 888888.88 },
123
+ { label: '境内', symbol: 'HK$', amount: dashboardAmounts.hkdDomestic },
124
+ { label: '境外', symbol: 'HK$', amount: dashboardAmounts.hkdOverseas },
105
125
  ],
106
126
  image: dashboardImage('07-hkd-hong-kong-skyline-bg.jpg'),
107
127
  accent: '#e06b00',
@@ -111,14 +131,18 @@ const cards: DashboardCard[] = [
111
131
  name: '日元',
112
132
  code: 'JPY',
113
133
  metrics: [
114
- { label: '境内', symbol: '¥', amount: 888888.88 },
115
- { label: '境外', symbol: '¥', amount: 888888.88 },
134
+ { label: '境内', symbol: '¥', amount: dashboardAmounts.jpyDomestic },
135
+ { label: '境外', symbol: '¥', amount: dashboardAmounts.jpyOverseas },
116
136
  ],
117
137
  image: dashboardImage('08-jpy-mount-fuji-bg.jpg'),
118
138
  accent: '#6125c8',
119
139
  position: 'center center',
120
140
  },
121
- ]
141
+ ])
142
+
143
+ onMounted(() => {
144
+ void loadDashboard()
145
+ })
122
146
 
123
147
  function getCardStyle(card: DashboardCard) {
124
148
  return {
@@ -150,11 +174,93 @@ function formatMoney(amount: number, symbol: string) {
150
174
 
151
175
  return `${symbol} ${formattedAmount}${unitSuffix}`
152
176
  }
177
+
178
+ async function loadDashboard() {
179
+ if (isDashboardLoading.value) return
180
+
181
+ isDashboardLoading.value = true
182
+ dashboardLoadError.value = ''
183
+
184
+ try {
185
+ const [rmbResult, foreignResult] = await Promise.allSettled([
186
+ queryRmbDashboard(),
187
+ queryForeignDashboard(),
188
+ ])
189
+ const errorMessages: string[] = []
190
+
191
+ if (rmbResult.status === 'fulfilled') {
192
+ applyRmbDashboardBody(rmbResult.value.body)
193
+ } else {
194
+ errorMessages.push(getErrorMessage(rmbResult.reason))
195
+ }
196
+
197
+ if (foreignResult.status === 'fulfilled') {
198
+ applyForeignDashboardBody(foreignResult.value.body)
199
+ } else {
200
+ errorMessages.push(getErrorMessage(foreignResult.reason))
201
+ }
202
+
203
+ if (errorMessages.length) {
204
+ dashboardLoadError.value = errorMessages.join(';')
205
+ }
206
+ } catch (error) {
207
+ console.error('[看板] 数据加载失败', error)
208
+ dashboardLoadError.value = getErrorMessage(error)
209
+ } finally {
210
+ isDashboardLoading.value = false
211
+ }
212
+ }
213
+
214
+ function applyRmbDashboardBody(body: Awaited<ReturnType<typeof queryRmbDashboard>>['body']) {
215
+ const result = body || {}
216
+
217
+ dashboardAmounts.rmb = toAmount(result.settleAccAmt)
218
+ dashboardAmounts.cityClearing = toAmount(result.availCredit)
219
+ dashboardAmounts.crossBorderRmb = toAmount(result.cipsAvailBal)
220
+ dashboardAmounts.digitalCurrency = toAmount(result.dcpTotalAmt)
221
+ }
222
+
223
+ function applyForeignDashboardBody(
224
+ body: Awaited<ReturnType<typeof queryForeignDashboard>>['body'],
225
+ ) {
226
+ const result = body || {}
227
+
228
+ dashboardAmounts.usdDomestic = toAmount(result.usdDomesticAmt)
229
+ dashboardAmounts.usdOverseas = toAmount(result.usdOverseasAmt)
230
+ dashboardAmounts.eurDomestic = toAmount(result.eurDomesticAmt)
231
+ dashboardAmounts.eurOverseas = toAmount(result.eurOverseasAmt)
232
+ dashboardAmounts.hkdDomestic = toAmount(result.hkdDomesticAmt)
233
+ dashboardAmounts.hkdOverseas = toAmount(result.hkdOverseasAmt)
234
+ dashboardAmounts.jpyDomestic = toAmount(result.jpyDomesticAmt)
235
+ dashboardAmounts.jpyOverseas = toAmount(result.jpyOverseasAmt)
236
+ }
237
+
238
+ function toAmount(value: unknown) {
239
+ const amount = Number(String(value ?? '').replaceAll(',', '').trim())
240
+
241
+ return Number.isFinite(amount) ? amount : 0
242
+ }
243
+
244
+ function getErrorMessage(error: unknown) {
245
+ if (error instanceof Error && error.message) return error.message
246
+
247
+ return '看板数据加载失败'
248
+ }
153
249
  </script>
154
250
 
155
251
  <template>
156
- <main class="dashboard-page">
252
+ <main class="dashboard-page" :aria-busy="isDashboardLoading">
157
253
  <div class="dashboard-toolbar" aria-label="看板设置">
254
+ <button
255
+ v-if="dashboardLoadError"
256
+ class="dashboard-retry"
257
+ type="button"
258
+ :disabled="isDashboardLoading"
259
+ @click="loadDashboard"
260
+ >
261
+ 刷新
262
+ </button>
263
+
158
264
  <label class="unit-switch">
159
265
  <span class="unit-switch__label">单位</span>
160
266
  <select v-model="selectedUnit" class="unit-switch__select" aria-label="切换金额单位">
@@ -180,8 +286,8 @@ function formatMoney(amount: number, symbol: string) {
180
286
  <span class="dashboard-card__code">{{ card.code }}</span>
181
287
  </header>
182
288
 
183
- <p v-if="card.amount" class="dashboard-card__amount">
184
- {{ formatMoney(card.amount, card.symbol || '') }}
289
+ <p v-if="!card.metrics" class="dashboard-card__amount">
290
+ {{ formatMoney(card.amount || 0, card.symbol || '') }}
185
291
  </p>
186
292
 
187
293
  <div v-else class="dashboard-card__metrics">
@@ -211,10 +317,29 @@ function formatMoney(amount: number, symbol: string) {
211
317
 
212
318
  .dashboard-toolbar {
213
319
  display: flex;
320
+ align-items: center;
214
321
  justify-content: flex-end;
215
322
  margin-bottom: 6px;
216
323
  }
217
324
 
325
+ .dashboard-retry {
326
+ height: 24px;
327
+ margin-right: auto;
328
+ border: 1px solid #ffd4d8;
329
+ border-radius: 5px;
330
+ padding: 0 10px;
331
+ color: #dc2735;
332
+ font: inherit;
333
+ font-size: 12px;
334
+ font-weight: 700;
335
+ line-height: 22px;
336
+ background: #fff5f6;
337
+ }
338
+
339
+ .dashboard-retry:disabled {
340
+ opacity: 0.62;
341
+ }
342
+
218
343
  .unit-switch {
219
344
  position: relative;
220
345
  display: inline-flex;
@@ -6,6 +6,7 @@ import {
6
6
  type ForeignForecastRecord,
7
7
  } from '@/api/foreign-position'
8
8
  import { useAppStore } from '@/stores/app'
9
+ import { usePositionFilterStore } from '@/stores/position-filter'
9
10
  import {
10
11
  allForeignBusinessOption,
11
12
  allForeignStatusOption,
@@ -54,6 +55,7 @@ type SelectorOption = ForeignOption | ForeignStatusOption
54
55
 
55
56
  const router = useRouter()
56
57
  const appStore = useAppStore()
58
+ const positionFilterStore = usePositionFilterStore()
57
59
  const defaultTranDate = appStore.systemWorkDate
58
60
  const heroImage = `${import.meta.env.BASE_URL}images/12a73787-86a9-4891-a65f-66104746f6a8.png`
59
61
  const pageSize = 10
@@ -74,7 +76,8 @@ const showDatePicker = ref(false)
74
76
  const datePickerValue = ref(selectedDate.value.split('-'))
75
77
  const minDate = new Date(2020, 0, 1)
76
78
  const maxDate = new Date(2030, 11, 31)
77
- const isFilterExpanded = ref(true)
79
+ // 筛选区展开状态由 Pinia 持久化;首次进入无缓存时默认收起。
80
+ const isFilterExpanded = computed(() => positionFilterStore.foreignFilterExpanded)
78
81
 
79
82
  const forecastList = ref<Forecast[]>([])
80
83
  const forecastTotal = ref(0)
@@ -183,6 +186,10 @@ function scrollToTop() {
183
186
  })
184
187
  }
185
188
 
189
+ function toggleFilterExpanded() {
190
+ positionFilterStore.setForeignFilterExpanded(!isFilterExpanded.value)
191
+ }
192
+
186
193
  function openCreatePage() {
187
194
  router.push({
188
195
  name: 'ForeignPositionCreate',
@@ -417,7 +424,7 @@ function isPendingForecast(value: Partial<PendingForecast> | null): value is Pen
417
424
  class="filter-toggle"
418
425
  type="button"
419
426
  :aria-expanded="isFilterExpanded"
420
- @click="isFilterExpanded = !isFilterExpanded"
427
+ @click="toggleFilterExpanded"
421
428
  >
422
429
  <van-icon :name="isFilterExpanded ? 'arrow-up' : 'arrow-down'" />
423
430
  <span>{{ isFilterExpanded ? '收起' : '展开' }}</span>
@@ -6,6 +6,7 @@ import {
6
6
  type RmbForecastRecord,
7
7
  } from '@/api/rmb-position'
8
8
  import { useAppStore } from '@/stores/app'
9
+ import { usePositionFilterStore } from '@/stores/position-filter'
9
10
  import {
10
11
  forecastStatusMap,
11
12
  formatForecastAmount,
@@ -41,6 +42,7 @@ type PendingForecast = {
41
42
 
42
43
  const router = useRouter()
43
44
  const appStore = useAppStore()
45
+ const positionFilterStore = usePositionFilterStore()
44
46
  const defaultTranDate = appStore.systemWorkDate
45
47
  const heroImage = `${import.meta.env.BASE_URL}images/5798d7aa-ba8b-4605-8079-58b35495ac55.png`
46
48
  const pageSize = 10
@@ -58,7 +60,8 @@ const showDatePicker = ref(false)
58
60
  const datePickerValue = ref(selectedDate.value.split('-'))
59
61
  const minDate = new Date(2020, 0, 1)
60
62
  const maxDate = new Date(2030, 11, 31)
61
- const isFilterExpanded = ref(true)
63
+ // 筛选区展开状态由 Pinia 持久化;首次进入无缓存时默认收起。
64
+ const isFilterExpanded = computed(() => positionFilterStore.rmbFilterExpanded)
62
65
 
63
66
  const forecastList = ref<Forecast[]>([])
64
67
  const forecastTotal = ref(0)
@@ -135,6 +138,10 @@ function scrollToTop() {
135
138
  })
136
139
  }
137
140
 
141
+ function toggleFilterExpanded() {
142
+ positionFilterStore.setRmbFilterExpanded(!isFilterExpanded.value)
143
+ }
144
+
138
145
  function openCreatePage() {
139
146
  router.push({
140
147
  name: 'RmbPositionCreate',
@@ -311,7 +318,7 @@ function isPendingForecast(value: Partial<PendingForecast> | null): value is Pen
311
318
  class="filter-toggle"
312
319
  type="button"
313
320
  :aria-expanded="isFilterExpanded"
314
- @click="isFilterExpanded = !isFilterExpanded"
321
+ @click="toggleFilterExpanded"
315
322
  >
316
323
  <van-icon :name="isFilterExpanded ? 'arrow-up' : 'arrow-down'" />
317
324
  <span>{{ isFilterExpanded ? '收起' : '展开' }}</span>
File without changes
File without changes
File without changes