accounting-page 0.1.11 → 0.1.16
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.
- package/README.md +63 -1
- package/index.html +1 -1
- package/package.json +8 -8
- package/src/App.vue +7 -9
- package/src/style.css +0 -296
- package/src/views/BalanceSheet.vue +48 -31
- package/src/views/VoucherCreate.vue +226 -90
- package/version.txt +1 -0
package/README.md
CHANGED
|
@@ -5,4 +5,66 @@ This template should help get you started developing with Vue 3 and TypeScript i
|
|
|
5
5
|
the [script setup docs](https://v3.vuejs.org/api/sfc-script-setup.html#sfc-script-setup) to learn more.
|
|
6
6
|
|
|
7
7
|
Learn more about the recommended Project Setup and IDE Support in
|
|
8
|
-
the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
|
8
|
+
the [Vue Docs TypeScript Guide](https://vuejs.org/guide/typescript/overview.html#project-setup).
|
|
9
|
+
|
|
10
|
+
## Run
|
|
11
|
+
|
|
12
|
+
### 环境
|
|
13
|
+
|
|
14
|
+
```shell
|
|
15
|
+
$$env:PATH+=";"+$env:USERPROFILE+"\AppData\Roaming\JetBrains\IntelliJIdea2026.1\node\versions\24.13.0\"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
### 新的开发
|
|
19
|
+
|
|
20
|
+
```shell
|
|
21
|
+
cd ~\IdeaProjects\ricewines\invest-page\accounting-page
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
```shell
|
|
25
|
+
$$env:INVEST_VERSION = (Get-Content "./version.txt" -Raw).Trim()
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
```shell
|
|
29
|
+
echo "已设置版本:$env:INVEST_VERSION"
|
|
30
|
+
echo "已设置版本:$env:PATH"
|
|
31
|
+
```
|
|
32
|
+
|
|
33
|
+
```shell
|
|
34
|
+
git add .
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
```shell
|
|
38
|
+
git commit -m "#16 升级"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
```shell
|
|
42
|
+
git tag -a v$env:INVEST_VERSION -m "发布版本$env:INVEST_VERSION"
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
```shell
|
|
46
|
+
git push origin v$env:INVEST_VERSION
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
```shell
|
|
50
|
+
git push origin dev_chixh
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
### 发布
|
|
54
|
+
|
|
55
|
+
```shell
|
|
56
|
+
npm login
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
```shell
|
|
60
|
+
npm publish
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
## TODO
|
|
64
|
+
|
|
65
|
+
- [X] 统计周期:本月首日—今日,报表类型选择资产负债表
|
|
66
|
+
- [X] 科目选择框设置为全屏/最大化
|
|
67
|
+
- [X] 凭证保存成功后,自动新建空白凭证窗口
|
|
68
|
+
- [ ] 科目选择框新增搜索功能(暂时搁置)
|
|
69
|
+
- [X] 金额支持小数点,计算精度保留到分
|
|
70
|
+
- [ ] 输入框点击后,无需聚焦。
|
package/index.html
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "accounting-page",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.16",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"scripts": {
|
|
6
6
|
"dev": "vite",
|
|
@@ -10,17 +10,17 @@
|
|
|
10
10
|
"update-check": "npm install --no-save npm-check && npm-check -u"
|
|
11
11
|
},
|
|
12
12
|
"dependencies": {
|
|
13
|
-
"axios": "^1.
|
|
13
|
+
"axios": "^1.17.0",
|
|
14
14
|
"vant": "^4.9.24",
|
|
15
|
-
"vue": "^3.5.
|
|
16
|
-
"vue-router": "^5.0
|
|
15
|
+
"vue": "^3.5.35",
|
|
16
|
+
"vue-router": "^5.1.0"
|
|
17
17
|
},
|
|
18
18
|
"devDependencies": {
|
|
19
|
-
"@types/node": "^
|
|
20
|
-
"@vitejs/plugin-vue": "^6.0.
|
|
19
|
+
"@types/node": "^25.9.2",
|
|
20
|
+
"@vitejs/plugin-vue": "^6.0.7",
|
|
21
21
|
"@vue/tsconfig": "^0.9.1",
|
|
22
22
|
"typescript": "~6.0.2",
|
|
23
|
-
"vite": "^8.0.
|
|
24
|
-
"vue-tsc": "^3.
|
|
23
|
+
"vite": "^8.0.16",
|
|
24
|
+
"vue-tsc": "^3.3.3"
|
|
25
25
|
}
|
|
26
26
|
}
|
package/src/App.vue
CHANGED
|
@@ -1,20 +1,18 @@
|
|
|
1
1
|
<script setup lang="ts">
|
|
2
2
|
import { ref } from 'vue'
|
|
3
|
-
|
|
4
|
-
|
|
3
|
+
/// 获取页面路径
|
|
4
|
+
console.log(""+location.pathname)
|
|
5
|
+
let pathname = ref(location.pathname)
|
|
6
|
+
const active = ref(pathname.value)
|
|
5
7
|
</script>
|
|
6
8
|
|
|
7
9
|
<template>
|
|
8
10
|
<router-view />
|
|
9
|
-
<van-tabbar v-model="active">
|
|
10
|
-
<van-tabbar-item icon="
|
|
11
|
-
<van-tabbar-item icon="
|
|
11
|
+
<van-tabbar v-model="active" fixed>
|
|
12
|
+
<van-tabbar-item icon="balance" to="/balance-sheet" name="/balance-sheet">资产负债表</van-tabbar-item>
|
|
13
|
+
<van-tabbar-item icon="orders" to="/voucher" name="/voucher">凭证记账</van-tabbar-item>
|
|
12
14
|
</van-tabbar>
|
|
13
15
|
</template>
|
|
14
16
|
|
|
15
17
|
<style>
|
|
16
|
-
body {
|
|
17
|
-
margin: 0;
|
|
18
|
-
padding: 0;
|
|
19
|
-
}
|
|
20
18
|
</style>
|
package/src/style.css
CHANGED
|
@@ -1,296 +0,0 @@
|
|
|
1
|
-
:root {
|
|
2
|
-
--text: #6b6375;
|
|
3
|
-
--text-h: #08060d;
|
|
4
|
-
--bg: #fff;
|
|
5
|
-
--border: #e5e4e7;
|
|
6
|
-
--code-bg: #f4f3ec;
|
|
7
|
-
--accent: #aa3bff;
|
|
8
|
-
--accent-bg: rgba(170, 59, 255, 0.1);
|
|
9
|
-
--accent-border: rgba(170, 59, 255, 0.5);
|
|
10
|
-
--social-bg: rgba(244, 243, 236, 0.5);
|
|
11
|
-
--shadow:
|
|
12
|
-
rgba(0, 0, 0, 0.1) 0 10px 15px -3px, rgba(0, 0, 0, 0.05) 0 4px 6px -2px;
|
|
13
|
-
|
|
14
|
-
--sans: system-ui, 'Segoe UI', Roboto, sans-serif;
|
|
15
|
-
--heading: system-ui, 'Segoe UI', Roboto, sans-serif;
|
|
16
|
-
--mono: ui-monospace, Consolas, monospace;
|
|
17
|
-
|
|
18
|
-
font: 18px/145% var(--sans);
|
|
19
|
-
letter-spacing: 0.18px;
|
|
20
|
-
color-scheme: light dark;
|
|
21
|
-
color: var(--text);
|
|
22
|
-
background: var(--bg);
|
|
23
|
-
font-synthesis: none;
|
|
24
|
-
text-rendering: optimizeLegibility;
|
|
25
|
-
-webkit-font-smoothing: antialiased;
|
|
26
|
-
-moz-osx-font-smoothing: grayscale;
|
|
27
|
-
|
|
28
|
-
@media (max-width: 1024px) {
|
|
29
|
-
font-size: 16px;
|
|
30
|
-
}
|
|
31
|
-
}
|
|
32
|
-
|
|
33
|
-
@media (prefers-color-scheme: dark) {
|
|
34
|
-
:root {
|
|
35
|
-
--text: #9ca3af;
|
|
36
|
-
--text-h: #f3f4f6;
|
|
37
|
-
--bg: #16171d;
|
|
38
|
-
--border: #2e303a;
|
|
39
|
-
--code-bg: #1f2028;
|
|
40
|
-
--accent: #c084fc;
|
|
41
|
-
--accent-bg: rgba(192, 132, 252, 0.15);
|
|
42
|
-
--accent-border: rgba(192, 132, 252, 0.5);
|
|
43
|
-
--social-bg: rgba(47, 48, 58, 0.5);
|
|
44
|
-
--shadow:
|
|
45
|
-
rgba(0, 0, 0, 0.4) 0 10px 15px -3px, rgba(0, 0, 0, 0.25) 0 4px 6px -2px;
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
#social .button-icon {
|
|
49
|
-
filter: invert(1) brightness(2);
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
|
|
53
|
-
body {
|
|
54
|
-
margin: 0;
|
|
55
|
-
}
|
|
56
|
-
|
|
57
|
-
h1,
|
|
58
|
-
h2 {
|
|
59
|
-
font-family: var(--heading);
|
|
60
|
-
font-weight: 500;
|
|
61
|
-
color: var(--text-h);
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
h1 {
|
|
65
|
-
font-size: 56px;
|
|
66
|
-
letter-spacing: -1.68px;
|
|
67
|
-
margin: 32px 0;
|
|
68
|
-
@media (max-width: 1024px) {
|
|
69
|
-
font-size: 36px;
|
|
70
|
-
margin: 20px 0;
|
|
71
|
-
}
|
|
72
|
-
}
|
|
73
|
-
h2 {
|
|
74
|
-
font-size: 24px;
|
|
75
|
-
line-height: 118%;
|
|
76
|
-
letter-spacing: -0.24px;
|
|
77
|
-
margin: 0 0 8px;
|
|
78
|
-
@media (max-width: 1024px) {
|
|
79
|
-
font-size: 20px;
|
|
80
|
-
}
|
|
81
|
-
}
|
|
82
|
-
p {
|
|
83
|
-
margin: 0;
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
code,
|
|
87
|
-
.counter {
|
|
88
|
-
font-family: var(--mono);
|
|
89
|
-
display: inline-flex;
|
|
90
|
-
border-radius: 4px;
|
|
91
|
-
color: var(--text-h);
|
|
92
|
-
}
|
|
93
|
-
|
|
94
|
-
code {
|
|
95
|
-
font-size: 15px;
|
|
96
|
-
line-height: 135%;
|
|
97
|
-
padding: 4px 8px;
|
|
98
|
-
background: var(--code-bg);
|
|
99
|
-
}
|
|
100
|
-
|
|
101
|
-
.counter {
|
|
102
|
-
font-size: 16px;
|
|
103
|
-
padding: 5px 10px;
|
|
104
|
-
border-radius: 5px;
|
|
105
|
-
color: var(--accent);
|
|
106
|
-
background: var(--accent-bg);
|
|
107
|
-
border: 2px solid transparent;
|
|
108
|
-
transition: border-color 0.3s;
|
|
109
|
-
margin-bottom: 24px;
|
|
110
|
-
|
|
111
|
-
&:hover {
|
|
112
|
-
border-color: var(--accent-border);
|
|
113
|
-
}
|
|
114
|
-
&:focus-visible {
|
|
115
|
-
outline: 2px solid var(--accent);
|
|
116
|
-
outline-offset: 2px;
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
.hero {
|
|
121
|
-
position: relative;
|
|
122
|
-
|
|
123
|
-
.base,
|
|
124
|
-
.framework,
|
|
125
|
-
.vite {
|
|
126
|
-
inset-inline: 0;
|
|
127
|
-
margin: 0 auto;
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
.base {
|
|
131
|
-
width: 170px;
|
|
132
|
-
position: relative;
|
|
133
|
-
z-index: 0;
|
|
134
|
-
}
|
|
135
|
-
|
|
136
|
-
.framework,
|
|
137
|
-
.vite {
|
|
138
|
-
position: absolute;
|
|
139
|
-
}
|
|
140
|
-
|
|
141
|
-
.framework {
|
|
142
|
-
z-index: 1;
|
|
143
|
-
top: 34px;
|
|
144
|
-
height: 28px;
|
|
145
|
-
transform: perspective(2000px) rotateZ(300deg) rotateX(44deg) rotateY(39deg)
|
|
146
|
-
scale(1.4);
|
|
147
|
-
}
|
|
148
|
-
|
|
149
|
-
.vite {
|
|
150
|
-
z-index: 0;
|
|
151
|
-
top: 107px;
|
|
152
|
-
height: 26px;
|
|
153
|
-
width: auto;
|
|
154
|
-
transform: perspective(2000px) rotateZ(300deg) rotateX(40deg) rotateY(39deg)
|
|
155
|
-
scale(0.8);
|
|
156
|
-
}
|
|
157
|
-
}
|
|
158
|
-
|
|
159
|
-
#app {
|
|
160
|
-
width: 1126px;
|
|
161
|
-
max-width: 100%;
|
|
162
|
-
margin: 0 auto;
|
|
163
|
-
text-align: center;
|
|
164
|
-
border-inline: 1px solid var(--border);
|
|
165
|
-
min-height: 100svh;
|
|
166
|
-
display: flex;
|
|
167
|
-
flex-direction: column;
|
|
168
|
-
box-sizing: border-box;
|
|
169
|
-
}
|
|
170
|
-
|
|
171
|
-
#center {
|
|
172
|
-
display: flex;
|
|
173
|
-
flex-direction: column;
|
|
174
|
-
gap: 25px;
|
|
175
|
-
place-content: center;
|
|
176
|
-
place-items: center;
|
|
177
|
-
flex-grow: 1;
|
|
178
|
-
|
|
179
|
-
@media (max-width: 1024px) {
|
|
180
|
-
padding: 32px 20px 24px;
|
|
181
|
-
gap: 18px;
|
|
182
|
-
}
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
#next-steps {
|
|
186
|
-
display: flex;
|
|
187
|
-
border-top: 1px solid var(--border);
|
|
188
|
-
text-align: left;
|
|
189
|
-
|
|
190
|
-
& > div {
|
|
191
|
-
flex: 1 1 0;
|
|
192
|
-
padding: 32px;
|
|
193
|
-
@media (max-width: 1024px) {
|
|
194
|
-
padding: 24px 20px;
|
|
195
|
-
}
|
|
196
|
-
}
|
|
197
|
-
|
|
198
|
-
.icon {
|
|
199
|
-
margin-bottom: 16px;
|
|
200
|
-
width: 22px;
|
|
201
|
-
height: 22px;
|
|
202
|
-
}
|
|
203
|
-
|
|
204
|
-
@media (max-width: 1024px) {
|
|
205
|
-
flex-direction: column;
|
|
206
|
-
text-align: center;
|
|
207
|
-
}
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
#docs {
|
|
211
|
-
border-right: 1px solid var(--border);
|
|
212
|
-
|
|
213
|
-
@media (max-width: 1024px) {
|
|
214
|
-
border-right: none;
|
|
215
|
-
border-bottom: 1px solid var(--border);
|
|
216
|
-
}
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
#next-steps ul {
|
|
220
|
-
list-style: none;
|
|
221
|
-
padding: 0;
|
|
222
|
-
display: flex;
|
|
223
|
-
gap: 8px;
|
|
224
|
-
margin: 32px 0 0;
|
|
225
|
-
|
|
226
|
-
.logo {
|
|
227
|
-
height: 18px;
|
|
228
|
-
}
|
|
229
|
-
|
|
230
|
-
a {
|
|
231
|
-
color: var(--text-h);
|
|
232
|
-
font-size: 16px;
|
|
233
|
-
border-radius: 6px;
|
|
234
|
-
background: var(--social-bg);
|
|
235
|
-
display: flex;
|
|
236
|
-
padding: 6px 12px;
|
|
237
|
-
align-items: center;
|
|
238
|
-
gap: 8px;
|
|
239
|
-
text-decoration: none;
|
|
240
|
-
transition: box-shadow 0.3s;
|
|
241
|
-
|
|
242
|
-
&:hover {
|
|
243
|
-
box-shadow: var(--shadow);
|
|
244
|
-
}
|
|
245
|
-
.button-icon {
|
|
246
|
-
height: 18px;
|
|
247
|
-
width: 18px;
|
|
248
|
-
}
|
|
249
|
-
}
|
|
250
|
-
|
|
251
|
-
@media (max-width: 1024px) {
|
|
252
|
-
margin-top: 20px;
|
|
253
|
-
flex-wrap: wrap;
|
|
254
|
-
justify-content: center;
|
|
255
|
-
|
|
256
|
-
li {
|
|
257
|
-
flex: 1 1 calc(50% - 8px);
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
a {
|
|
261
|
-
width: 100%;
|
|
262
|
-
justify-content: center;
|
|
263
|
-
box-sizing: border-box;
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
#spacer {
|
|
269
|
-
height: 88px;
|
|
270
|
-
border-top: 1px solid var(--border);
|
|
271
|
-
@media (max-width: 1024px) {
|
|
272
|
-
height: 48px;
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
.ticks {
|
|
277
|
-
position: relative;
|
|
278
|
-
width: 100%;
|
|
279
|
-
|
|
280
|
-
&::before,
|
|
281
|
-
&::after {
|
|
282
|
-
content: '';
|
|
283
|
-
position: absolute;
|
|
284
|
-
top: -4.5px;
|
|
285
|
-
border: 5px solid transparent;
|
|
286
|
-
}
|
|
287
|
-
|
|
288
|
-
&::before {
|
|
289
|
-
left: 0;
|
|
290
|
-
border-left-color: var(--border);
|
|
291
|
-
}
|
|
292
|
-
&::after {
|
|
293
|
-
right: 0;
|
|
294
|
-
border-right-color: var(--border);
|
|
295
|
-
}
|
|
296
|
-
}
|
|
@@ -1,66 +1,78 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div
|
|
3
|
-
<van-nav-bar title="
|
|
2
|
+
<div>
|
|
3
|
+
<van-nav-bar title="资产负债" fixed/>
|
|
4
|
+
<van-nav-bar title="资产负债" />
|
|
4
5
|
|
|
5
|
-
<van-
|
|
6
|
-
<van-field
|
|
6
|
+
<van-field
|
|
7
7
|
v-model="startDate"
|
|
8
8
|
label="开始日期"
|
|
9
9
|
type="date"
|
|
10
10
|
value-format="YYYY-MM-DD"
|
|
11
|
-
|
|
12
|
-
|
|
11
|
+
/>
|
|
12
|
+
<van-field
|
|
13
13
|
v-model="endDate"
|
|
14
14
|
label="结束日期"
|
|
15
15
|
type="date"
|
|
16
16
|
value-format="YYYY-MM-DD"
|
|
17
|
-
|
|
18
|
-
</van-cell-group>
|
|
17
|
+
/>
|
|
19
18
|
|
|
20
19
|
<van-button
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
style="margin: 15px"
|
|
25
|
-
>
|
|
26
|
-
查询报表
|
|
20
|
+
type="primary"
|
|
21
|
+
block
|
|
22
|
+
@click="loadData">查询报表
|
|
27
23
|
</van-button>
|
|
28
24
|
|
|
29
25
|
<van-cell-group title="资产">
|
|
30
26
|
<van-cell
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
27
|
+
v-for="item in assets"
|
|
28
|
+
:key="item.code"
|
|
29
|
+
:title="item.name"
|
|
30
|
+
:value="item.balance"
|
|
35
31
|
/>
|
|
36
32
|
</van-cell-group>
|
|
37
33
|
|
|
38
34
|
<van-cell-group title="负债">
|
|
39
35
|
<van-cell
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
36
|
+
v-for="item in liabilities"
|
|
37
|
+
:key="item.code"
|
|
38
|
+
:title="item.name"
|
|
39
|
+
:value="item.balance"
|
|
44
40
|
/>
|
|
45
41
|
</van-cell-group>
|
|
46
42
|
|
|
47
43
|
<van-cell-group title="权益">
|
|
48
44
|
<van-cell
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
45
|
+
v-for="item in equity"
|
|
46
|
+
:key="item.code"
|
|
47
|
+
:title="item.name"
|
|
48
|
+
:value="item.balance"
|
|
53
49
|
/>
|
|
54
50
|
</van-cell-group>
|
|
51
|
+
-->
|
|
55
52
|
</div>
|
|
56
53
|
</template>
|
|
57
54
|
|
|
58
55
|
<script lang="ts" setup>
|
|
59
|
-
import { ref
|
|
56
|
+
import {onMounted, ref} from 'vue'
|
|
60
57
|
import axios from 'axios'
|
|
61
58
|
|
|
62
|
-
|
|
63
|
-
const
|
|
59
|
+
// 自动获取:本月首日
|
|
60
|
+
const getMonthFirstDay = () => {
|
|
61
|
+
// 传入 Date,返回【当前系统时区】的 toISOString 格式日期(YYYY-MM-DD)
|
|
62
|
+
let now =new Date()
|
|
63
|
+
let date =new Date(now.getFullYear(), now.getMonth() ,1)
|
|
64
|
+
return new Date(date.getTime() - date.getTimezoneOffset() * 60000)
|
|
65
|
+
.toISOString().split('T')[0];
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// 自动获取:今日
|
|
69
|
+
const getToday = () => {
|
|
70
|
+
return new Date().toISOString().slice(0, 10)
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
// 默认值:本月首日 — 今日
|
|
74
|
+
const startDate = ref(getMonthFirstDay())
|
|
75
|
+
const endDate = ref(getToday())
|
|
64
76
|
|
|
65
77
|
const assets = ref<Array<{ code: string; name: string; balance: number }>>([])
|
|
66
78
|
const liabilities = ref<Array<{ code: string; name: string; balance: number }>>([])
|
|
@@ -68,15 +80,20 @@ const equity = ref<Array<{ code: string; name: string; balance: number }>>([])
|
|
|
68
80
|
|
|
69
81
|
const loadData = () => {
|
|
70
82
|
axios.get('/balance-sheet', {
|
|
71
|
-
params: {
|
|
83
|
+
params: {startDate: startDate.value, endDate: endDate.value}
|
|
72
84
|
}).then(res => {
|
|
73
85
|
assets.value = res.data.assets
|
|
74
86
|
liabilities.value = res.data.liabilities
|
|
75
87
|
equity.value = res.data.equity
|
|
76
88
|
})
|
|
77
89
|
}
|
|
90
|
+
|
|
91
|
+
// 进入页面自动加载一次
|
|
92
|
+
onMounted(() => {
|
|
93
|
+
loadData()
|
|
94
|
+
})
|
|
78
95
|
</script>
|
|
79
96
|
|
|
97
|
+
|
|
80
98
|
<style scoped>
|
|
81
|
-
.page { padding: 46px 0 0; }
|
|
82
99
|
</style>
|
|
@@ -1,61 +1,62 @@
|
|
|
1
1
|
<template>
|
|
2
|
-
<div class="page">
|
|
2
|
+
<div class="page-container">
|
|
3
3
|
<van-nav-bar title="凭证记账" fixed />
|
|
4
4
|
|
|
5
|
-
<van-form
|
|
5
|
+
<van-form @submit="submit" class="content">
|
|
6
6
|
<van-field
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
7
|
+
v-model="voucher.voucherNo"
|
|
8
|
+
label="凭证号"
|
|
9
|
+
placeholder="自动生成"
|
|
10
|
+
readonly
|
|
11
11
|
/>
|
|
12
12
|
<van-field
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
v-model="voucher.voucherDate"
|
|
14
|
+
label="日期"
|
|
15
|
+
type="date"
|
|
16
|
+
value-format="YYYY-MM-DD"
|
|
17
17
|
/>
|
|
18
18
|
<van-field
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
19
|
+
v-model="voucher.description"
|
|
20
|
+
label="摘要"
|
|
21
|
+
placeholder="输入备注"
|
|
22
22
|
/>
|
|
23
23
|
|
|
24
24
|
<div class="title">凭证明细(可添加多笔)</div>
|
|
25
25
|
|
|
26
26
|
<div
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
27
|
+
v-for="(entry, index) in entries"
|
|
28
|
+
:key="index"
|
|
29
|
+
class="entry-card"
|
|
30
30
|
>
|
|
31
31
|
<van-cell-group :border="false">
|
|
32
32
|
<van-field
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
33
|
+
:model-value="entry.accountName"
|
|
34
|
+
label="科目"
|
|
35
|
+
placeholder="点击选择科目"
|
|
36
|
+
is-link
|
|
37
|
+
@click="openAccountPicker(index)"
|
|
38
38
|
/>
|
|
39
|
+
<!-- 改为支持小数 -->
|
|
39
40
|
<van-field
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
41
|
+
v-model="entry.debit"
|
|
42
|
+
label="借方金额"
|
|
43
|
+
type="number"
|
|
44
|
+
@change="calcTotal"
|
|
44
45
|
/>
|
|
45
46
|
<van-field
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
47
|
+
v-model="entry.credit"
|
|
48
|
+
label="贷方金额"
|
|
49
|
+
type="number"
|
|
50
|
+
@change="calcTotal"
|
|
50
51
|
/>
|
|
51
52
|
</van-cell-group>
|
|
52
53
|
|
|
53
|
-
<div style="text-align: right; padding:
|
|
54
|
+
<div style="text-align: right; padding: 4px 12px">
|
|
54
55
|
<van-button
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
size="small"
|
|
57
|
+
type="danger"
|
|
58
|
+
plain
|
|
59
|
+
@click="removeEntry(index)"
|
|
59
60
|
>
|
|
60
61
|
删除此行
|
|
61
62
|
</van-button>
|
|
@@ -63,11 +64,11 @@
|
|
|
63
64
|
</div>
|
|
64
65
|
|
|
65
66
|
<van-button
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
67
|
+
type="primary"
|
|
68
|
+
plain
|
|
69
|
+
block
|
|
70
|
+
@click="addEntry"
|
|
71
|
+
style="margin: 12px 0"
|
|
71
72
|
>
|
|
72
73
|
+ 添加一笔分录
|
|
73
74
|
</van-button>
|
|
@@ -78,21 +79,46 @@
|
|
|
78
79
|
</van-cell-group>
|
|
79
80
|
|
|
80
81
|
<van-button
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
82
|
+
type="primary"
|
|
83
|
+
block
|
|
84
|
+
native-type="submit"
|
|
85
|
+
style="margin-top: 16px"
|
|
85
86
|
>
|
|
86
87
|
保存凭证(借贷必须相等)
|
|
87
88
|
</van-button>
|
|
88
89
|
</van-form>
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
<!-- 左右分栏科目选择弹窗 -->
|
|
92
|
+
<van-popup v-model:show="showAccountPicker" position="center" round class="account-popup">
|
|
93
|
+
<div class="picker-header">
|
|
94
|
+
<span class="picker-title">选择会计科目</span>
|
|
95
|
+
<van-icon name="cross" size="22" @click="showAccountPicker = false" />
|
|
96
|
+
</div>
|
|
97
|
+
<div class="split-picker">
|
|
98
|
+
<!-- 左侧分类 -->
|
|
99
|
+
<div class="left-category">
|
|
100
|
+
<div
|
|
101
|
+
v-for="cate in categoryList"
|
|
102
|
+
:key="cate.id"
|
|
103
|
+
class="cate-item"
|
|
104
|
+
:class="{ active: activeCateId === cate.id }"
|
|
105
|
+
@click="switchCategory(cate.id)"
|
|
106
|
+
>
|
|
107
|
+
{{ cate.name }}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
<!-- 右侧科目列表 -->
|
|
111
|
+
<div class="right-account">
|
|
112
|
+
<div
|
|
113
|
+
v-for="acc in currentAccountList"
|
|
114
|
+
:key="acc.id"
|
|
115
|
+
class="acc-item"
|
|
116
|
+
@click="selectAccount(acc)"
|
|
117
|
+
>
|
|
118
|
+
{{ acc.text }}
|
|
119
|
+
</div>
|
|
120
|
+
</div>
|
|
121
|
+
</div>
|
|
96
122
|
</van-popup>
|
|
97
123
|
</div>
|
|
98
124
|
</template>
|
|
@@ -103,17 +129,37 @@ import { showToast } from 'vant'
|
|
|
103
129
|
import axios from 'axios'
|
|
104
130
|
|
|
105
131
|
const showAccountPicker = ref(false)
|
|
106
|
-
const accountList = ref([])
|
|
107
132
|
const currentEntryIndex = ref(0)
|
|
133
|
+
const rawAccountList = ref<any[]>([])
|
|
134
|
+
const categoryList = ref<any[]>([])
|
|
135
|
+
const currentAccountList = ref<any[]>([])
|
|
136
|
+
const activeCateId = ref<number>()
|
|
137
|
+
|
|
138
|
+
const totalDebit = ref(0)
|
|
139
|
+
const totalCredit = ref(0)
|
|
140
|
+
|
|
141
|
+
// 初始化空白凭证
|
|
142
|
+
const initVoucher = () => {
|
|
143
|
+
return {
|
|
144
|
+
voucherNo: 'PZ' + new Date().getTime(),
|
|
145
|
+
voucherDate: getLocalISODate(new Date()),
|
|
146
|
+
description: '',
|
|
147
|
+
status: '草稿',
|
|
148
|
+
ifrsBasis: 'IFRS',
|
|
149
|
+
entries: []
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
|
|
153
|
+
// 本地时区日期
|
|
154
|
+
const getLocalISODate = (date: Date) => {
|
|
155
|
+
return new Date(date.getTime() - date.getTimezoneOffset() * 60000).toISOString().split('T')[0]
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
const voucher = reactive<Voucher>(initVoucher())
|
|
159
|
+
const entries = ref([
|
|
160
|
+
{ accountName: '', accountId: null, debit: '', credit: '', remark: '' }
|
|
161
|
+
])
|
|
108
162
|
|
|
109
|
-
const voucher = reactive<Voucher>({
|
|
110
|
-
voucherNo: 'PZ' + new Date().getTime(),
|
|
111
|
-
voucherDate: new Date().toISOString().slice(0, 10),
|
|
112
|
-
description: '',
|
|
113
|
-
status: '草稿',
|
|
114
|
-
ifrsBasis: 'IFRS',
|
|
115
|
-
entries: []
|
|
116
|
-
})
|
|
117
163
|
interface Voucher {
|
|
118
164
|
voucherNo: string
|
|
119
165
|
voucherDate: string
|
|
@@ -123,40 +169,60 @@ interface Voucher {
|
|
|
123
169
|
entries: any[]
|
|
124
170
|
}
|
|
125
171
|
|
|
126
|
-
|
|
127
|
-
const entries = ref([
|
|
128
|
-
{ accountName: '', accountId: null, debit: 0, credit: 0, remark: '' }
|
|
129
|
-
])
|
|
130
|
-
|
|
131
|
-
const totalDebit = ref(0)
|
|
132
|
-
const totalCredit = ref(0)
|
|
133
|
-
|
|
172
|
+
// 加载科目
|
|
134
173
|
onMounted(() => {
|
|
135
174
|
axios.get('/account/accounts').then(res => {
|
|
136
|
-
|
|
175
|
+
rawAccountList.value = res.data.map((item: { code: string; name: string; id: number; type?: string }) => ({
|
|
137
176
|
text: `${item.code} ${item.name}`,
|
|
138
|
-
value: item.id
|
|
177
|
+
value: item.id,
|
|
178
|
+
category: item.type
|
|
139
179
|
}))
|
|
180
|
+
splitCategory(rawAccountList.value)
|
|
140
181
|
})
|
|
141
182
|
})
|
|
142
183
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
184
|
+
// 分类处理
|
|
185
|
+
const splitCategory = (list: { category: string }[]) => {
|
|
186
|
+
const cateMap = new Map<string, { id: number; name: string }>()
|
|
187
|
+
list.forEach(item => {
|
|
188
|
+
const cateName = item.category || '其他'
|
|
189
|
+
if (!cateMap.has(cateName)) {
|
|
190
|
+
cateMap.set(cateName, { id: cateMap.size + 1, name: cateName })
|
|
191
|
+
}
|
|
192
|
+
})
|
|
193
|
+
categoryList.value = Array.from(cateMap.values())
|
|
194
|
+
if (categoryList.value.length) {
|
|
195
|
+
activeCateId.value = categoryList.value[0].id
|
|
196
|
+
if (activeCateId.value) switchCategory(activeCateId.value)
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// 切换分类
|
|
201
|
+
const switchCategory = (cateId: number) => {
|
|
202
|
+
activeCateId.value = cateId
|
|
203
|
+
const targetCate = categoryList.value.find(c => c.id === cateId)
|
|
204
|
+
currentAccountList.value = rawAccountList.value.filter(item => item.category === targetCate?.name)
|
|
146
205
|
}
|
|
147
206
|
|
|
148
|
-
|
|
207
|
+
// 选择科目
|
|
208
|
+
const selectAccount = (acc: any) => {
|
|
149
209
|
const entry = entries.value[currentEntryIndex.value]
|
|
150
|
-
entry.accountName =
|
|
151
|
-
entry.accountId =
|
|
210
|
+
entry.accountName = acc.text
|
|
211
|
+
entry.accountId = acc.value
|
|
152
212
|
showAccountPicker.value = false
|
|
153
213
|
}
|
|
154
214
|
|
|
215
|
+
const openAccountPicker = (index: number) => {
|
|
216
|
+
currentEntryIndex.value = index
|
|
217
|
+
showAccountPicker.value = true
|
|
218
|
+
}
|
|
219
|
+
|
|
155
220
|
const addEntry = () => {
|
|
156
221
|
entries.value.push({
|
|
157
|
-
accountName: '', accountId: null, debit:
|
|
222
|
+
accountName: '', accountId: null, debit: '', credit: '', remark: ''
|
|
158
223
|
})
|
|
159
224
|
}
|
|
225
|
+
|
|
160
226
|
const removeEntry = (index: number) => {
|
|
161
227
|
if (entries.value.length === 1) {
|
|
162
228
|
showToast('至少保留一笔分录')
|
|
@@ -166,17 +232,21 @@ const removeEntry = (index: number) => {
|
|
|
166
232
|
calcTotal()
|
|
167
233
|
}
|
|
168
234
|
|
|
235
|
+
// 精确计算(保留2位小数)
|
|
169
236
|
const calcTotal = () => {
|
|
170
237
|
let debit = 0
|
|
171
238
|
let credit = 0
|
|
172
239
|
entries.value.forEach(e => {
|
|
173
|
-
debit +=
|
|
174
|
-
credit +=
|
|
240
|
+
debit += Math.round((parseFloat(e.debit) || 0) * 100)
|
|
241
|
+
credit += Math.round((parseFloat(e.credit) || 0) * 100)
|
|
175
242
|
})
|
|
176
|
-
totalDebit.value = debit
|
|
177
|
-
totalCredit.value = credit
|
|
243
|
+
totalDebit.value = Math.round(debit) / 100
|
|
244
|
+
totalCredit.value = Math.round(credit) / 100
|
|
178
245
|
}
|
|
179
246
|
|
|
247
|
+
// ==============================================
|
|
248
|
+
// 保存成功 → 自动新建空白凭证(核心功能)
|
|
249
|
+
// ==============================================
|
|
180
250
|
const submit = () => {
|
|
181
251
|
calcTotal()
|
|
182
252
|
if (totalDebit.value !== totalCredit.value) {
|
|
@@ -186,13 +256,19 @@ const submit = () => {
|
|
|
186
256
|
|
|
187
257
|
voucher.entries = entries.value.map(e => ({
|
|
188
258
|
account: { id: e.accountId },
|
|
189
|
-
debit: e.debit,
|
|
190
|
-
credit: e.credit,
|
|
259
|
+
debit: parseFloat(e.debit) || 0,
|
|
260
|
+
credit: parseFloat(e.credit) || 0,
|
|
191
261
|
description: voucher.description
|
|
192
262
|
}))
|
|
193
263
|
|
|
194
264
|
axios.post('/voucher/create', voucher).then(() => {
|
|
195
265
|
showToast('✅ 凭证保存成功')
|
|
266
|
+
|
|
267
|
+
// 自动新开空白凭证
|
|
268
|
+
Object.assign(voucher, initVoucher())
|
|
269
|
+
entries.value = [{ accountName: '', accountId: null, debit: '', credit: '', remark: '' }]
|
|
270
|
+
calcTotal()
|
|
271
|
+
|
|
196
272
|
}).catch(err => {
|
|
197
273
|
showToast('❌ 保存失败')
|
|
198
274
|
console.error(err)
|
|
@@ -201,21 +277,81 @@ const submit = () => {
|
|
|
201
277
|
</script>
|
|
202
278
|
|
|
203
279
|
<style scoped>
|
|
204
|
-
.page {
|
|
205
|
-
|
|
280
|
+
.page-container {
|
|
281
|
+
position: fixed;
|
|
282
|
+
top: 0;
|
|
283
|
+
left: 0;
|
|
284
|
+
right: 0;
|
|
285
|
+
bottom: 0;
|
|
286
|
+
overflow-y: auto;
|
|
287
|
+
padding-top: 46px;
|
|
288
|
+
box-sizing: border-box;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
.content {
|
|
292
|
+
padding: 0 12px 20px;
|
|
206
293
|
}
|
|
294
|
+
|
|
207
295
|
.title {
|
|
208
|
-
padding: 10px;
|
|
296
|
+
padding: 10px 2px;
|
|
209
297
|
font-weight: bold;
|
|
210
|
-
color: #323233;
|
|
211
298
|
}
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
}
|
|
215
|
-
.entry-group {
|
|
299
|
+
|
|
300
|
+
.entry-card {
|
|
216
301
|
background: #f7f8fa;
|
|
217
302
|
border-radius: 8px;
|
|
218
|
-
padding:
|
|
303
|
+
padding: 8px;
|
|
219
304
|
margin-bottom: 10px;
|
|
220
305
|
}
|
|
306
|
+
|
|
307
|
+
.account-popup {
|
|
308
|
+
width: 90%;
|
|
309
|
+
height: 70vh;
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
.picker-header {
|
|
313
|
+
display: flex;
|
|
314
|
+
justify-content: space-between;
|
|
315
|
+
align-items: center;
|
|
316
|
+
padding: 12px 16px;
|
|
317
|
+
border-bottom: 1px solid #eee;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.picker-title {
|
|
321
|
+
font-size: 16px;
|
|
322
|
+
font-weight: 500;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
.split-picker {
|
|
326
|
+
display: flex;
|
|
327
|
+
height: calc(100% - 48px);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
.left-category {
|
|
331
|
+
width: 35%;
|
|
332
|
+
background: #f5f5f5;
|
|
333
|
+
overflow-y: auto;
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
.cate-item {
|
|
337
|
+
padding: 14px 10px;
|
|
338
|
+
text-align: center;
|
|
339
|
+
font-size: 14px;
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
.cate-item.active {
|
|
343
|
+
background: #fff;
|
|
344
|
+
color: #1989fa;
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
.right-account {
|
|
348
|
+
flex: 1;
|
|
349
|
+
overflow-y: auto;
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
.acc-item {
|
|
353
|
+
padding: 14px 16px;
|
|
354
|
+
border-bottom: 1px solid #f5f5f5;
|
|
355
|
+
font-size: 14px;
|
|
356
|
+
}
|
|
221
357
|
</style>
|
package/version.txt
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
0.1.16
|