vue2server7 7.0.50 → 7.0.52

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.
File without changes
@@ -0,0 +1,262 @@
1
+ <template>
2
+ <div class="money-input">
3
+ <el-input
4
+ v-model="inputDisplayValue"
5
+ :class="{ 'is-error': showError }"
6
+ :placeholder="placeholder"
7
+ :disabled="disabled"
8
+ clearable
9
+ @input="onInput"
10
+ @focus="onFocus"
11
+ @blur="onBlur"
12
+ @clear="onClear"
13
+ />
14
+ <span v-if="showError" class="money-input__error">{{ errorMessage }}</span>
15
+ </div>
16
+ </template>
17
+
18
+ <script setup lang="ts">
19
+ import { computed, ref, watch } from 'vue'
20
+ import { ElMessageBox } from 'element-plus'
21
+
22
+ const FORMAT_REG = /^(?:0|[1-9]\d{0,15})\.\d{2}$/
23
+
24
+ const props = withDefaults(defineProps<{
25
+ modelValue?: string | null
26
+ placeholder?: string
27
+ disabled?: boolean
28
+ errorMessage?: string
29
+ showChineseUppercase?: boolean
30
+ }>(), {
31
+ modelValue: null,
32
+ placeholder: '请输入金额',
33
+ disabled: false,
34
+ errorMessage: '金额格式应为最多 16 位整数 + 2 位小数(如 1.00)',
35
+ showChineseUppercase: false
36
+ })
37
+
38
+ const emit = defineEmits<{
39
+ 'update:modelValue': [value: string | null]
40
+ 'validate': [valid: boolean]
41
+ }>()
42
+
43
+ const displayValue = ref('')
44
+ const focused = ref(false)
45
+ const hasBlurred = ref(false)
46
+
47
+ // 千分位格式化(用于显示)
48
+ function toThousands(numStr: string): string {
49
+ if (!numStr) return ''
50
+ const [intPart, decPart] = numStr.split('.')
51
+ const formatted = intPart.replace(/\B(?=(\d{3})+(?!\d))/g, ',')
52
+ return decPart ? `${formatted}.${decPart}` : formatted
53
+ }
54
+
55
+ // 移除千分位(用于编辑)
56
+ function fromThousands(str: string): string {
57
+ return str.replace(/,/g, '')
58
+ }
59
+
60
+ const inputDisplayValue = computed(() => {
61
+ if (!displayValue.value) return ''
62
+ return focused.value ? displayValue.value : toThousands(displayValue.value)
63
+ })
64
+
65
+ const isValid = computed(() => {
66
+ if (!displayValue.value) return true
67
+ return FORMAT_REG.test(displayValue.value)
68
+ })
69
+
70
+ const showError = computed(() => hasBlurred.value && !focused.value && !isValid.value)
71
+
72
+ function normalizeIntegerPart(rawInt: string): string {
73
+ const digits = rawInt.replace(/\D/g, '').slice(0, 16)
74
+ if (!digits) return ''
75
+ const trimmed = digits.replace(/^0+/, '')
76
+ return trimmed || '0'
77
+ }
78
+
79
+ function sanitizeInput(raw: string): string {
80
+ if (!raw) return ''
81
+
82
+ const cleaned = raw.replace(/[^\d.]/g, '')
83
+ const dotIndex = cleaned.indexOf('.')
84
+
85
+ if (dotIndex === -1) {
86
+ return normalizeIntegerPart(cleaned)
87
+ }
88
+
89
+ const intPart = normalizeIntegerPart(cleaned.slice(0, dotIndex))
90
+ const decPart = cleaned.slice(dotIndex + 1).replace(/\./g, '').slice(0, 2)
91
+ return `${intPart || '0'}.${decPart}`
92
+ }
93
+
94
+ function formatToMoney(raw: string): string {
95
+ const sanitized = sanitizeInput(raw)
96
+ if (!sanitized) return ''
97
+
98
+ const [intPartRaw, decPartRaw = ''] = sanitized.split('.')
99
+ const intPart = normalizeIntegerPart(intPartRaw || '0') || '0'
100
+ const decPart = decPartRaw.padEnd(2, '0').slice(0, 2)
101
+ return `${intPart}.${decPart}`
102
+ }
103
+
104
+ function emitValue() {
105
+ const val = displayValue.value || null
106
+ emit('update:modelValue', val)
107
+ emit('validate', !val || FORMAT_REG.test(val))
108
+ }
109
+
110
+ function onInput(val: string | number) {
111
+ // 输入时移除千分位逗号
112
+ displayValue.value = sanitizeInput(fromThousands(String(val)))
113
+ }
114
+
115
+ function onFocus() {
116
+ focused.value = true
117
+ hasBlurred.value = false
118
+ }
119
+
120
+ function numberToChineseUppercase(numStr: string): string {
121
+ if (!numStr) return ''
122
+
123
+ const num = parseFloat(numStr)
124
+ if (isNaN(num) || num < 0) return ''
125
+
126
+ const fraction = ['角', '分']
127
+ const digit = ['零', '壹', '贰', '叁', '肆', '伍', '陆', '柒', '捌', '玖']
128
+ const unit = [['元', '万', '亿'], ['', '拾', '佰', '仟']]
129
+
130
+ let s = ''
131
+ const parts = numStr.split('.')
132
+ const intPart = parts[0]
133
+ const decPart = parts[1] || ''
134
+
135
+ // 处理整数部分
136
+ let intNum = parseInt(intPart)
137
+ if (intNum === 0) {
138
+ s = '零元'
139
+ } else {
140
+ let tmp = ''
141
+ let zeroCount = 0
142
+
143
+ for (let i = 0; i < intPart.length; i++) {
144
+ const p = intPart.length - 1 - i
145
+ const d = parseInt(intPart[i])
146
+
147
+ if (d === 0) {
148
+ zeroCount++
149
+ } else {
150
+ if (zeroCount > 0) {
151
+ tmp += digit[0]
152
+ }
153
+ zeroCount = 0
154
+ tmp += digit[d] + unit[1][p % 4]
155
+ }
156
+
157
+ if (p % 4 === 0 && zeroCount < 4) {
158
+ const unitIndex = Math.floor(p / 4)
159
+ if (unitIndex < unit[0].length) {
160
+ tmp += unit[0][unitIndex]
161
+ zeroCount = 0
162
+ }
163
+ }
164
+ }
165
+
166
+ s = tmp.replace(/零+$/, '') + '元'
167
+ }
168
+
169
+ // 处理小数部分
170
+ if (decPart) {
171
+ for (let i = 0; i < decPart.length && i < 2; i++) {
172
+ const d = parseInt(decPart[i])
173
+ if (d !== 0) {
174
+ s += digit[d] + fraction[i]
175
+ }
176
+ }
177
+ }
178
+
179
+ if (s === '零元') s = '零元整'
180
+ else if (!decPart || decPart === '00') s += '整'
181
+
182
+ return s
183
+ }
184
+
185
+ function onBlur() {
186
+ focused.value = false
187
+ hasBlurred.value = true
188
+ if (displayValue.value) {
189
+ displayValue.value = formatToMoney(displayValue.value)
190
+ }
191
+ emitValue()
192
+
193
+ // 如果启用了中文大写显示,则弹出消息框
194
+ if (props.showChineseUppercase && displayValue.value) {
195
+ const chineseUppercase = numberToChineseUppercase(displayValue.value)
196
+ if (chineseUppercase) {
197
+ ElMessageBox.alert(
198
+ chineseUppercase,
199
+ '金额大写',
200
+ {
201
+ confirmButtonText: '确定',
202
+ type: 'info'
203
+ }
204
+ )
205
+ }
206
+ }
207
+ }
208
+
209
+ function onClear() {
210
+ displayValue.value = ''
211
+ hasBlurred.value = false
212
+ emitValue()
213
+ }
214
+
215
+ function validate(): boolean {
216
+ hasBlurred.value = true
217
+ if (displayValue.value) {
218
+ displayValue.value = formatToMoney(displayValue.value)
219
+ emitValue()
220
+ }
221
+ return !displayValue.value || FORMAT_REG.test(displayValue.value)
222
+ }
223
+
224
+ function reset() {
225
+ displayValue.value = ''
226
+ hasBlurred.value = false
227
+ emitValue()
228
+ }
229
+
230
+ defineExpose({ validate, reset })
231
+
232
+ watch(
233
+ () => props.modelValue,
234
+ (val) => {
235
+ const next = val ? formatToMoney(String(val)) : ''
236
+ if (next !== displayValue.value) {
237
+ displayValue.value = next
238
+ }
239
+ },
240
+ { immediate: true }
241
+ )
242
+ </script>
243
+
244
+ <style scoped>
245
+ .money-input {
246
+ display: flex;
247
+ flex-direction: column;
248
+ width: 100%;
249
+ min-width: 0;
250
+ }
251
+
252
+ .is-error :deep(.el-input__wrapper) {
253
+ box-shadow: 0 0 0 1px var(--el-color-danger) inset;
254
+ }
255
+
256
+ .money-input__error {
257
+ color: var(--el-color-danger);
258
+ font-size: 12px;
259
+ line-height: 1.4;
260
+ padding-top: 4px;
261
+ }
262
+ </style>
@@ -0,0 +1,101 @@
1
+ <template>
2
+ <section class="page money-input-page">
3
+ <h1 class="title">金额输入组件</h1>
4
+ <p class="desc">
5
+ 固定金额格式:整数最多 16 位,小数固定 2 位,禁止前导零(如 <code>01.00</code>)。
6
+ </p>
7
+
8
+ <el-card class="demo-card" shadow="never">
9
+ <template #header>
10
+ <span>基础示例</span>
11
+ </template>
12
+
13
+ <el-form label-width="100px">
14
+ <el-form-item label="金额">
15
+ <MoneyInput
16
+ :showChineseUppercase="true"
17
+ ref="moneyInputRef"
18
+ v-model="amount"
19
+ placeholder="请输入金额,如 1.00"
20
+ @validate="onValidate"
21
+ />
22
+ </el-form-item>
23
+ </el-form>
24
+
25
+ <div class="demo-output">
26
+ 当前值:<code>{{ amount ?? 'null' }}</code>
27
+ </div>
28
+ <div class="demo-output">
29
+ 校验状态:<code>{{ valid ? '通过' : '不通过' }}</code>
30
+ </div>
31
+
32
+ <div class="demo-actions">
33
+ <el-button type="primary" @click="doValidate">手动校验</el-button>
34
+ <el-button @click="doReset">重置</el-button>
35
+ </div>
36
+ </el-card>
37
+ </section>
38
+ </template>
39
+
40
+ <script setup lang="ts">
41
+ import { ref } from 'vue'
42
+ import MoneyInput from '../components/MoneyInput.vue'
43
+
44
+ const moneyInputRef = ref<InstanceType<typeof MoneyInput>>()
45
+ const amount = ref<string | null>(null)
46
+ const valid = ref(true)
47
+
48
+ function onValidate(v: boolean) {
49
+ valid.value = v
50
+ }
51
+
52
+ function doValidate() {
53
+ valid.value = moneyInputRef.value?.validate() ?? true
54
+ }
55
+
56
+ function doReset() {
57
+ moneyInputRef.value?.reset()
58
+ amount.value = null
59
+ valid.value = true
60
+ }
61
+ </script>
62
+
63
+ <style scoped>
64
+ .page.money-input-page {
65
+ padding: 20px 24px;
66
+ max-width: 720px;
67
+ }
68
+
69
+ .title {
70
+ margin: 0 0 8px;
71
+ font-size: 20px;
72
+ font-weight: 600;
73
+ }
74
+
75
+ .desc {
76
+ margin: 0 0 20px;
77
+ color: var(--el-text-color-secondary);
78
+ font-size: 14px;
79
+ line-height: 1.5;
80
+ }
81
+
82
+ .demo-card {
83
+ margin-bottom: 16px;
84
+ }
85
+
86
+ .demo-output {
87
+ margin-top: 12px;
88
+ font-size: 13px;
89
+ color: var(--el-text-color-regular);
90
+ }
91
+
92
+ .demo-output code {
93
+ font-size: 12px;
94
+ }
95
+
96
+ .demo-actions {
97
+ margin-top: 12px;
98
+ display: flex;
99
+ gap: 8px;
100
+ }
101
+ </style>
@@ -5,6 +5,7 @@ import ImportTablePage from '../pages/ImportTablePage.vue'
5
5
  import PositionReportPage from '../pages/PositionReportPage.vue'
6
6
  import DateRangePage from '../pages/DateRangePage.vue'
7
7
  import OrgTreeSelectPage from '../pages/OrgTreeSelectPage.vue'
8
+ import MoneyInputPage from '../pages/MoneyInputPage.vue'
8
9
 
9
10
  export const routes = [
10
11
  {
@@ -73,5 +74,14 @@ export const routes = [
73
74
  title: '机构树选择',
74
75
  showInMenu: true
75
76
  }
77
+ },
78
+ {
79
+ path: '/money-input',
80
+ name: 'MoneyInput',
81
+ component: MoneyInputPage,
82
+ meta: {
83
+ title: '金额输入',
84
+ showInMenu: true
85
+ }
76
86
  }
77
87
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue2server7",
3
- "version": "7.0.50",
3
+ "version": "7.0.52",
4
4
  "description": "",
5
5
  "scripts": {
6
6
  "dev": "nodemon --watch src --ext ts --exec \"ts-node src/app.ts\"",
package/test/111 DELETED
@@ -1,52 +0,0 @@
1
- // src/directives/permission.ts
2
- import type { Directive } from 'vue'
3
-
4
- function getPermissions(): string[] {
5
- return JSON.parse(localStorage.getItem('permissions') || '[]')
6
- }
7
-
8
- export const permissionDirective: Directive = {
9
- mounted(el, binding) {
10
- const { value } = binding
11
- const permissions = getPermissions()
12
-
13
- if (!value) return
14
-
15
- const hasPermission = Array.isArray(value)
16
- ? value.some(p => permissions.includes(p))
17
- : permissions.includes(value)
18
-
19
- if (!hasPermission) {
20
- el.parentNode?.removeChild(el)
21
- }
22
- }
23
- }
24
-
25
-
26
- // main.ts
27
- import { createApp } from 'vue'
28
- import App from './App.vue'
29
- import { permissionDirective } from '@/directives/permission'
30
-
31
- const app = createApp(App)
32
-
33
- app.directive('permission', permissionDirective)
34
-
35
- app.mount('#app')
36
-
37
-
38
- <el-button v-permission="'user:add'">新增</el-button>
39
-
40
- <el-button v-permission="['user:edit', 'user:update']">
41
- 编辑
42
- </el-button>
43
-
44
-
45
-
46
-
47
- <el-button v-permission="'user:add'">新增</el-button>
48
-
49
- <el-button v-permission="['user:edit', 'user:update']">
50
- 编辑
51
- </el-button>
52
-
package/test/111.txt DELETED
@@ -1,44 +0,0 @@
1
- import axios from "axios";
2
-
3
- const service = axios.create({
4
- baseURL: import.meta.env.VITE_API_BASE,
5
- timeout: 15000,
6
- headers: {
7
- "Content-Type": "application/json",
8
- },
9
- });
10
-
11
- // 分页字段
12
- const PAGE_KEYS = ["pageIndex", "pageSize"];
13
-
14
- // 拆分函数
15
- function splitHeadBody(params = {}, tranCode) {
16
- const head = {
17
- tranCode, // 关键:接口地址放进 head.tranCode
18
- };
19
- const body = {};
20
-
21
- Object.keys(params).forEach((key) => {
22
- if (PAGE_KEYS.includes(key)) {
23
- head[key] = params[key];
24
- } else {
25
- body[key] = params[key];
26
- }
27
- });
28
-
29
- return { head, body };
30
- }
31
-
32
- const request = {
33
- post(tranCode, params = {}, config = {}) {
34
- const { head, body } = splitHeadBody(params, tranCode);
35
-
36
- return service.post(
37
- "/gateway", // 实际统一请求地址
38
- { head, body },
39
- config
40
- );
41
- },
42
- };
43
-
44
- export default request;
package/test/11111111111 DELETED
@@ -1,24 +0,0 @@
1
- let el = $0
2
- while (el) {
3
- if (el.__vueParentComponent) {
4
- console.log('找到了 Vue3 实例:', el)
5
- console.log(el.__vueParentComponent)
6
- break
7
- }
8
- el = el.parentElement
9
- }
10
-
11
- el.__vueParentComponent.setupState
12
-
13
- let el = $0
14
- while (el) {
15
- const comp = el.__vueParentComponent
16
- if (comp) {
17
- const data = comp.setupState || comp.ctx
18
- if (data && data.forrcats) {
19
- console.log('找到了 forrcats:', data.forrcats)
20
- break
21
- }
22
- }
23
- el = el.parentElement
24
- }
@@ -1,60 +0,0 @@
1
- // scripts/check-permission-duplicate.js
2
- import fs from 'fs'
3
- import path from 'path'
4
-
5
- const rootDir = path.resolve(process.cwd(), 'src')
6
-
7
- const permissionMap = new Map()
8
-
9
- function scanFile(filePath) {
10
- const content = fs.readFileSync(filePath, 'utf-8')
11
-
12
- // 匹配 v-permission="xxx" 或 v-permission="'xxx'"
13
- const regex = /v-permission\s*=\s*["']([^"']+)["']/g
14
-
15
- let match
16
- while ((match = regex.exec(content))) {
17
- const permission = match[1].trim()
18
-
19
- if (!permissionMap.has(permission)) {
20
- permissionMap.set(permission, [])
21
- }
22
-
23
- permissionMap.get(permission).push(filePath)
24
- }
25
- }
26
-
27
- function scanDir(dir) {
28
- const files = fs.readdirSync(dir)
29
-
30
- for (const file of files) {
31
- const fullPath = path.join(dir, file)
32
- const stat = fs.statSync(fullPath)
33
-
34
- if (stat.isDirectory()) {
35
- scanDir(fullPath)
36
- } else if (/\.(vue|js|ts|jsx|tsx)$/.test(file)) {
37
- scanFile(fullPath)
38
- }
39
- }
40
- }
41
-
42
- scanDir(rootDir)
43
-
44
- // 输出重复
45
- console.log('\n🔍 重复的权限指令如下:\n')
46
-
47
- let hasDuplicate = false
48
-
49
- for (const [key, files] of permissionMap.entries()) {
50
- if (files.length > 1) {
51
- hasDuplicate = true
52
- console.log(`🚨 权限:${key}`)
53
- files.forEach(f => console.log(` - ${f}`))
54
- console.log('')
55
- }
56
- }
57
-
58
- if (!hasDuplicate) {
59
- console.log('✅ 没有发现重复权限')
60
- }
package/test/12.js DELETED
@@ -1,115 +0,0 @@
1
- const fs = require("fs");
2
-
3
- const content = fs.readFileSync("input.txt", "utf8");
4
-
5
- // Java → TS 类型
6
- function javaToTsType(javaType) {
7
- const map = {
8
- String: "string",
9
- Integer: "number",
10
- Long: "number",
11
- Double: "number",
12
- Float: "number",
13
- BigDecimal: "number",
14
- Boolean: "boolean",
15
- Date: "string",
16
- LocalDate: "string",
17
- LocalDateTime: "string",
18
- };
19
- return map[javaType] || "any";
20
- }
21
-
22
- // TS 类型默认值
23
- function defaultValue(tsType) {
24
- switch (tsType) {
25
- case "string":
26
- return `""`;
27
- case "number":
28
- return `0`;
29
- case "boolean":
30
- return `false`;
31
- default:
32
- return `null`;
33
- }
34
- }
35
-
36
- /**
37
- * 解析策略:
38
- * - 按“字段声明”逐个找:private Type field;
39
- * - 然后回看它前面一段文本(最多回看 10 行左右)抓 label
40
- * - label 优先级:ApiModelProperty.value > CheckV.desc > 兜底 field 名
41
- */
42
- const fieldRegex = /private\s+(\w+)\s+(\w+)\s*;/g;
43
-
44
- const lines = content.split(/\r?\n/);
45
- const jsonResult = [];
46
- const tsFields = [];
47
- const formFields = [];
48
-
49
- // 为了“回看”,我们把每一行的起始偏移算出来,方便从 match.index 找到行号
50
- const lineStartOffsets = [];
51
- let offset = 0;
52
- for (const line of lines) {
53
- lineStartOffsets.push(offset);
54
- offset += line.length + 1; // +1 for \n(\r\n 也足够用来定位)
55
- }
56
-
57
- function findLineIndexByOffset(idx) {
58
- // 二分找 <= idx 的最大 lineStartOffsets
59
- let l = 0, r = lineStartOffsets.length - 1, ans = 0;
60
- while (l <= r) {
61
- const m = (l + r) >> 1;
62
- if (lineStartOffsets[m] <= idx) { ans = m; l = m + 1; }
63
- else r = m - 1;
64
- }
65
- return ans;
66
- }
67
-
68
- function extractLabelAround(lineIndex) {
69
- // 往上最多找 12 行(你可以按需调大)
70
- const start = Math.max(0, lineIndex - 12);
71
- const window = lines.slice(start, lineIndex).join("\n");
72
-
73
- // 先找 ApiModelProperty(value="xxx")
74
- const api = /@ApiModelProperty\s*\(\s*value\s*=\s*"([^"]+)"/.exec(window);
75
- if (api?.[1]) return api[1];
76
-
77
- // 再找 CheckV(desc="xxx")
78
- const check = /@CheckV\s*\(\s*desc\s*=\s*"([^"]+)"/.exec(window);
79
- if (check?.[1]) return check[1];
80
-
81
- return null;
82
- }
83
-
84
- let match;
85
- while ((match = fieldRegex.exec(content)) !== null) {
86
- const javaType = match[1];
87
- const field = match[2];
88
-
89
- const lineIndex = findLineIndexByOffset(match.index);
90
- const label = extractLabelAround(lineIndex) || field;
91
-
92
- const tsType = javaToTsType(javaType);
93
-
94
- jsonResult.push({ field, label });
95
-
96
- tsFields.push(` /** ${label} */\n ${field}: ${tsType};`);
97
- formFields.push(` /** ${label} */\n ${field}: ${defaultValue(tsType)}`);
98
- }
99
-
100
- // 写文件
101
- fs.writeFileSync("output.json", JSON.stringify(jsonResult, null, 2), "utf8");
102
-
103
- fs.writeFileSync(
104
- "model.ts",
105
- `export interface Model {\n${tsFields.join("\n\n")}\n}\n`,
106
- "utf8"
107
- );
108
-
109
- fs.writeFileSync(
110
- "form.js",
111
- `export const formModel = {\n${formFields.join(",\n\n")}\n};\n`,
112
- "utf8"
113
- );
114
-
115
- console.log("✅ 已生成 output.json / model.ts / form.js");
Binary file
Binary file
Binary file