verce-vue-test 0.0.2 → 0.0.6

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "verce-vue-test",
3
- "version": "0.0.2",
3
+ "version": "0.0.6",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -9,14 +9,14 @@
9
9
  "preview": "vite preview",
10
10
  "build-only": "vite build",
11
11
  "type-check": "vue-tsc --build",
12
- "发布": "npm publish",
12
+ "发布": "npm publish",
13
13
  "修订 ": "npm version patch",
14
14
  "次版本": "npm version minor",
15
15
  "主版本": "npm version major",
16
16
  "登录": "npm login ",
17
17
  "一键发布": "npm version patch && npm publish"
18
18
  },
19
- "keywords": [
19
+ "keywords": [
20
20
  "express",
21
21
  "web",
22
22
  "application"
@@ -43,7 +43,7 @@
43
43
  "engines": {
44
44
  "node": "^20.19.0 || >=22.12.0"
45
45
  },
46
- "publishConfig": {
46
+ "publishConfig": {
47
47
  "access": "public",
48
48
  "registry": "https://registry.npmjs.org"
49
49
  }
package/src/App.vue CHANGED
@@ -30,6 +30,12 @@ import { RouterView } from 'vue-router'
30
30
  <el-menu-item index="/table-pro-for">
31
31
  <span>循环表格</span>
32
32
  </el-menu-item>
33
+ <el-menu-item index="/employee">
34
+ <span>员工管理</span>
35
+ </el-menu-item>
36
+ <el-menu-item index="/thousandth">
37
+ <span>千分位输入</span>
38
+ </el-menu-item>
33
39
  <el-menu-item index="/about">
34
40
  <span>关于</span>
35
41
  </el-menu-item>
@@ -0,0 +1,285 @@
1
+ <script setup lang="ts">
2
+ import { ref, reactive, watch, nextTick, computed } from 'vue'
3
+ import type { FormInstance, FormRules } from 'element-plus'
4
+
5
+ /**
6
+ * 基于 Element Plus 的表单弹窗组件
7
+ * 支持新增/编辑模式,通过 fields 配置动态渲染表单项
8
+ * 支持 input、select、number、date、textarea、radio、switch 等字段类型
9
+ */
10
+ defineOptions({
11
+ name: 'ElFormDialog',
12
+ inheritAttrs: false, // 不自动将 $attrs 绑定到根元素,避免属性透传到 el-dialog
13
+ })
14
+
15
+ /** 表单字段类型 */
16
+ type FieldType = 'input' | 'select' | 'number' | 'date' | 'textarea' | 'radio' | 'switch'
17
+
18
+ /** 表单字段配置项 */
19
+ type FormField = {
20
+ /** 字段属性名,对应表单数据中的 key */
21
+ prop: string
22
+ /** 字段标签文本 */
23
+ label: string
24
+ /** 字段类型 */
25
+ type: FieldType
26
+ /** 占位提示文本,不传则根据类型自动生成 */
27
+ placeholder?: string
28
+ /** 是否必填,为 true 时自动生成必填校验规则 */
29
+ required?: boolean
30
+ /** 自定义校验规则,优先级低于 props.rules 中同名字段的规则 */
31
+ rules?: FormRules[string]
32
+ /** 下拉选项列表,适用于 select 和 radio 类型 */
33
+ options?: Array<{ label: string; value: any }>
34
+ /** 数字输入框最小值,适用于 number 类型 */
35
+ min?: number
36
+ /** 数字输入框最大值,适用于 number 类型 */
37
+ max?: number
38
+ /** 文本域行数,适用于 textarea 类型,默认 3 */
39
+ rows?: number
40
+ /** switch 打开时的文字描述 */
41
+ activeText?: string
42
+ /** switch 关闭时的文字描述 */
43
+ inactiveText?: string
44
+ }
45
+
46
+ const props = withDefaults(
47
+ defineProps<{
48
+ /** 控制弹窗显示/隐藏,支持 v-model 双向绑定 */
49
+ modelValue: boolean
50
+ /** 表单字段配置列表 */
51
+ fields: FormField[]
52
+ /** 表单数据对象,编辑时需传入包含 id 的对象,新增时传入空对象 */
53
+ formData: Record<string, any>
54
+ /** 弹窗标题,不传则根据是否编辑自动显示"新增"或"编辑" */
55
+ title?: string
56
+ /** 弹窗宽度 */
57
+ width?: string
58
+ /** 自定义校验规则,与 fields 中的 required/rules 合并,同名字段优先使用此处的规则 */
59
+ rules?: FormRules
60
+ }>(),
61
+ {
62
+ width: '600px',
63
+ },
64
+ )
65
+
66
+ const emit = defineEmits<{
67
+ /** 弹窗显示状态变化时触发,用于 v-model 双向绑定 */
68
+ 'update:modelValue': [value: boolean]
69
+ /** 点击确认按钮且表单校验通过后触发,参数为表单数据(编辑时包含 id) */
70
+ confirm: [data: Record<string, any>]
71
+ }>()
72
+
73
+ /** 表单实例引用,用于调用 validate、clearValidate 等方法 */
74
+ const formRef = ref<FormInstance>()
75
+
76
+ /**
77
+ * 弹窗显示状态的双向绑定计算属性
78
+ * 读取时返回 props.modelValue,设置时触发 update:modelValue 事件
79
+ */
80
+ const dialogVisible = computed({
81
+ get: () => props.modelValue,
82
+ set: value => emit('update:modelValue', value),
83
+ })
84
+
85
+ /** 是否为编辑模式(根据 formData 中是否包含 id 判断) */
86
+ const isEdit = computed(() => !!props.formData?.id)
87
+
88
+ /** 弹窗标题,优先使用 props.title,否则编辑模式显示"编辑"、新增模式显示"新增" */
89
+ const dialogTitle = computed(() => {
90
+ if (props.title) return props.title
91
+ return isEdit.value ? '编辑' : '新增'
92
+ })
93
+
94
+ /** 响应式表单数据对象,根据 fields 配置动态初始化 */
95
+ const form = reactive<Record<string, any>>({})
96
+
97
+ /**
98
+ * 合并后的表单校验规则
99
+ * 合并逻辑:
100
+ * 1. 以 props.rules 为基础
101
+ * 2. 若字段配置了 required 且 props.rules 中无对应规则,则自动生成必填规则
102
+ * 3. 若字段配置了自定义 rules 且 props.rules 中无对应规则,则使用字段的自定义规则
103
+ * 注:props.rules 中的规则优先级最高,不会被字段级配置覆盖
104
+ */
105
+ const mergedRules = computed(() => {
106
+ const rules: FormRules = { ...props.rules }
107
+ props.fields.forEach(field => {
108
+ if (field.required && !rules[field.prop]) {
109
+ rules[field.prop] = [
110
+ {
111
+ required: true,
112
+ // 根据字段类型自动生成提示语:select/date 用"选择",其他用"输入"
113
+ message: `请${field.type === 'select' || field.type === 'date' ? '选择' : '输入'}${field.label}`,
114
+ // 根据字段类型自动选择触发方式:switch/select/date 用 change,其他用 blur
115
+ trigger: field.type === 'switch' ? 'change' : field.type === 'select' || field.type === 'date' ? 'change' : 'blur',
116
+ },
117
+ ]
118
+ }
119
+ if (field.rules && !rules[field.prop]) {
120
+ rules[field.prop] = field.rules
121
+ }
122
+ })
123
+ return rules
124
+ })
125
+
126
+ /**
127
+ * 初始化表单数据
128
+ * 1. 清空原有表单数据
129
+ * 2. 根据 fields 配置逐字段初始化,使用 formData 中对应值或默认值
130
+ * - switch 类型默认 false
131
+ * - number 类型默认 undefined
132
+ * - 其他类型默认空字符串
133
+ */
134
+ const initForm = () => {
135
+ Object.keys(form).forEach(key => delete form[key])
136
+ props.fields.forEach(field => {
137
+ if (field.type === 'switch') {
138
+ form[field.prop] = props.formData?.[field.prop] ?? false
139
+ } else if (field.type === 'number') {
140
+ form[field.prop] = props.formData?.[field.prop] ?? undefined
141
+ } else {
142
+ form[field.prop] = props.formData?.[field.prop] ?? ''
143
+ }
144
+ })
145
+ }
146
+
147
+ /**
148
+ * 监听弹窗打开事件
149
+ * 弹窗打开时:初始化表单数据,并在下一帧清除校验状态(避免残留的校验提示)
150
+ */
151
+ watch(
152
+ () => props.modelValue,
153
+ val => {
154
+ if (val) {
155
+ initForm()
156
+ nextTick(() => formRef.value?.clearValidate())
157
+ }
158
+ },
159
+ )
160
+
161
+ /**
162
+ * 确认按钮点击处理
163
+ * 1. 校验表单
164
+ * 2. 校验通过后触发 confirm 事件,传递表单数据(编辑时附带 id)
165
+ * 3. 关闭弹窗
166
+ */
167
+ const handleConfirm = async () => {
168
+ if (!formRef.value) return
169
+ await formRef.value.validate(valid => {
170
+ if (!valid) return
171
+ emit('confirm', { ...form, id: props.formData?.id })
172
+ dialogVisible.value = false
173
+ })
174
+ }
175
+
176
+ /** 取消按钮点击处理,直接关闭弹窗 */
177
+ const handleCancel = () => {
178
+ dialogVisible.value = false
179
+ }
180
+ </script>
181
+
182
+ <template>
183
+ <!-- 弹窗容器:禁止点击遮罩关闭、关闭时销毁内容 -->
184
+ <el-dialog
185
+ v-model="dialogVisible"
186
+ :title="dialogTitle"
187
+ :width="width"
188
+ :close-on-click-modal="false"
189
+ destroy-on-close
190
+ >
191
+ <!-- 表单容器 -->
192
+ <el-form
193
+ ref="formRef"
194
+ :model="form"
195
+ :rules="mergedRules"
196
+ label-width="100px"
197
+ >
198
+ <!-- 动态渲染表单项,根据 field.type 选择对应的输入组件 -->
199
+ <el-form-item
200
+ v-for="field in fields"
201
+ :key="field.prop"
202
+ :label="field.label"
203
+ :prop="field.prop"
204
+ >
205
+ <!-- 文本输入框 -->
206
+ <el-input
207
+ v-if="field.type === 'input'"
208
+ v-model="form[field.prop]"
209
+ :placeholder="field.placeholder || `请输入${field.label}`"
210
+ />
211
+
212
+ <!-- 数字输入框 -->
213
+ <el-input-number
214
+ v-else-if="field.type === 'number'"
215
+ v-model="form[field.prop]"
216
+ :min="field.min"
217
+ :max="field.max"
218
+ :placeholder="field.placeholder || `请输入${field.label}`"
219
+ style="width: 100%"
220
+ />
221
+
222
+ <!-- 下拉选择框 -->
223
+ <el-select
224
+ v-else-if="field.type === 'select'"
225
+ v-model="form[field.prop]"
226
+ :placeholder="field.placeholder || `请选择${field.label}`"
227
+ style="width: 100%"
228
+ >
229
+ <el-option
230
+ v-for="opt in field.options"
231
+ :key="opt.value"
232
+ :label="opt.label"
233
+ :value="opt.value"
234
+ />
235
+ </el-select>
236
+
237
+ <!-- 日期选择器 -->
238
+ <el-date-picker
239
+ v-else-if="field.type === 'date'"
240
+ v-model="form[field.prop]"
241
+ type="date"
242
+ :placeholder="field.placeholder || `请选择${field.label}`"
243
+ style="width: 100%"
244
+ />
245
+
246
+ <!-- 文本域 -->
247
+ <el-input
248
+ v-else-if="field.type === 'textarea'"
249
+ v-model="form[field.prop]"
250
+ type="textarea"
251
+ :rows="field.rows || 3"
252
+ :placeholder="field.placeholder || `请输入${field.label}`"
253
+ />
254
+
255
+ <!-- 开关 -->
256
+ <el-switch
257
+ v-else-if="field.type === 'switch'"
258
+ v-model="form[field.prop]"
259
+ :active-text="field.activeText"
260
+ :inactive-text="field.inactiveText"
261
+ />
262
+
263
+ <!-- 单选框组 -->
264
+ <el-radio-group
265
+ v-else-if="field.type === 'radio'"
266
+ v-model="form[field.prop]"
267
+ >
268
+ <el-radio
269
+ v-for="opt in field.options"
270
+ :key="opt.value"
271
+ :value="opt.value"
272
+ >
273
+ {{ opt.label }}
274
+ </el-radio>
275
+ </el-radio-group>
276
+ </el-form-item>
277
+ </el-form>
278
+
279
+ <!-- 底部按钮区域 -->
280
+ <template #footer>
281
+ <el-button @click="handleCancel">取消</el-button>
282
+ <el-button type="primary" @click="handleConfirm">确认</el-button>
283
+ </template>
284
+ </el-dialog>
285
+ </template>
@@ -0,0 +1,103 @@
1
+ <script setup lang="ts">
2
+ import { ref, watch, nextTick } from 'vue'
3
+
4
+ defineOptions({ name: 'ElInputThousandth' })
5
+
6
+ const props = withDefaults(
7
+ defineProps<{
8
+ modelValue?: number | string
9
+ placeholder?: string
10
+ disabled?: boolean
11
+ }>(),
12
+ {
13
+ modelValue: undefined,
14
+ placeholder: '',
15
+ disabled: false,
16
+ },
17
+ )
18
+
19
+ const emit = defineEmits<{
20
+ 'update:modelValue': [value: number | string | undefined]
21
+ }>()
22
+
23
+ const isFocused = ref(false)
24
+ const displayValue = ref('')
25
+ const inputRef = ref<InstanceType<typeof import('element-plus')['ElInput']>>()
26
+
27
+ function formatThousandth(val: string | number | undefined): string {
28
+ if (val === undefined || val === null || val === '') return ''
29
+ const str = String(val)
30
+ const num = Number(str)
31
+ if (isNaN(num)) return ''
32
+ const parts = str.split('.')
33
+ const intPart = parts[0].replace(/\B(?=(\d{3})+(?!\d))/g, ',')
34
+ if (parts.length > 1) {
35
+ return intPart + '.' + parts[1]
36
+ }
37
+ return intPart
38
+ }
39
+
40
+ function stripNonNumeric(val: string): string {
41
+ let result = val.replace(/[^\d.]/g, '')
42
+ const dotIndex = result.indexOf('.')
43
+ if (dotIndex !== -1) {
44
+ result = result.slice(0, dotIndex + 1) + result.slice(dotIndex + 1).replace(/\./g, '')
45
+ }
46
+ if (dotIndex !== -1) {
47
+ const parts = result.split('.')
48
+ if (parts[1].length > 2) {
49
+ result = parts[0] + '.' + parts[1].slice(0, 2)
50
+ }
51
+ }
52
+ return result
53
+ }
54
+
55
+ function handleInput(val: string) {
56
+ const cleaned = stripNonNumeric(val)
57
+ displayValue.value = cleaned
58
+ if (cleaned === '' || cleaned === '.') {
59
+ emit('update:modelValue', undefined)
60
+ } else {
61
+ emit('update:modelValue', Number(cleaned))
62
+ }
63
+ }
64
+
65
+ function handleFocus() {
66
+ isFocused.value = true
67
+ const raw = props.modelValue !== undefined && props.modelValue !== null && props.modelValue !== ''
68
+ ? String(props.modelValue)
69
+ : ''
70
+ displayValue.value = raw
71
+ }
72
+
73
+ function handleBlur() {
74
+ isFocused.value = false
75
+ if (props.modelValue !== undefined && props.modelValue !== null && props.modelValue !== '') {
76
+ displayValue.value = formatThousandth(props.modelValue)
77
+ } else {
78
+ displayValue.value = ''
79
+ }
80
+ }
81
+
82
+ watch(
83
+ () => props.modelValue,
84
+ (val) => {
85
+ if (!isFocused.value) {
86
+ displayValue.value = formatThousandth(val)
87
+ }
88
+ },
89
+ { immediate: true },
90
+ )
91
+ </script>
92
+
93
+ <template>
94
+ <el-input
95
+ ref="inputRef"
96
+ :model-value="displayValue"
97
+ :placeholder="placeholder"
98
+ :disabled="disabled"
99
+ @input="handleInput"
100
+ @focus="handleFocus"
101
+ @blur="handleBlur"
102
+ />
103
+ </template>
@@ -300,7 +300,7 @@ const initColumns = () => {
300
300
  return {
301
301
  key: getColumnKey(vnode, index),
302
302
  label: getColumnLabel(vnode, index),
303
- visible: true,
303
+ visible: columnProps?.visible !== false,
304
304
  disabled: columnProps?.disabled ?? false,
305
305
  vnode,
306
306
  }
@@ -39,6 +39,16 @@ const router = createRouter({
39
39
  name: 'tableProFor',
40
40
  component: () => import('../views/TableProForView.vue'),
41
41
  },
42
+ {
43
+ path: '/employee',
44
+ name: 'employee',
45
+ component: () => import('../views/EmployeeView.vue'),
46
+ },
47
+ {
48
+ path: '/thousandth',
49
+ name: 'thousandth',
50
+ component: () => import('../views/ThousandthView.vue'),
51
+ },
42
52
  ],
43
53
  })
44
54
 
@@ -0,0 +1,204 @@
1
+ <template>
2
+ <ElTablePro
3
+ :data="tableData"
4
+ :total="total"
5
+ v-model:page="query.page"
6
+ v-model:page-size="query.pageSize"
7
+ table-key="employee"
8
+ row-key="id"
9
+ border
10
+ stripe
11
+ v-loading="loading"
12
+ @pagination-change="handlePaginationChange"
13
+ >
14
+ <template #toolbar>
15
+ <el-button type="primary" @click="handleAdd">新增</el-button>
16
+ </template>
17
+
18
+ <el-table-column prop="id" label="ID" width="80" disabled />
19
+ <el-table-column prop="name" label="姓名" width="120" />
20
+ <el-table-column prop="department" label="部门" width="120">
21
+ <template #default="{ row }">
22
+ <el-tag :type="deptTagType(row.department)">
23
+ {{ row.department }}
24
+ </el-tag>
25
+ </template>
26
+ </el-table-column>
27
+ <el-table-column prop="position" label="职位" />
28
+ <el-table-column prop="salary" label="薪资" width="120">
29
+ <template #default="{ row }">
30
+ ¥{{ row.salary.toLocaleString() }}
31
+ </template>
32
+ </el-table-column>
33
+ <el-table-column prop="status" label="状态" width="100">
34
+ <template #default="{ row }">
35
+ <el-tag :type="row.status ? 'success' : 'danger'">
36
+ {{ row.status ? '在职' : '离职' }}
37
+ </el-tag>
38
+ </template>
39
+ </el-table-column>
40
+ <el-table-column label="操作" width="180">
41
+ <template #default="{ row }">
42
+ <el-button link type="primary" @click="handleEdit(row)">编辑</el-button>
43
+ <el-button link type="danger" @click="handleDelete(row)">删除</el-button>
44
+ </template>
45
+ </el-table-column>
46
+ </ElTablePro>
47
+
48
+ <ElFormDialog
49
+ v-model="dialogVisible"
50
+ :fields="formFields"
51
+ :form-data="currentRow"
52
+ title="员工信息"
53
+ @confirm="handleFormConfirm"
54
+ />
55
+ </template>
56
+
57
+ <script setup lang="ts">
58
+ import { ref, reactive, onMounted } from 'vue'
59
+ import { ElMessage, ElMessageBox } from 'element-plus'
60
+ import ElTablePro from '@/components/ElTablePro.vue'
61
+ import ElFormDialog from '@/components/ElFormDialog.vue'
62
+ import type { FormRules } from 'element-plus'
63
+
64
+ type Employee = {
65
+ id: number
66
+ name: string
67
+ department: string
68
+ position: string
69
+ salary: number
70
+ status: boolean
71
+ }
72
+
73
+ const formFields = [
74
+ { prop: 'name', label: '姓名', type: 'input' as const, required: true },
75
+ {
76
+ prop: 'department',
77
+ label: '部门',
78
+ type: 'select' as const,
79
+ required: true,
80
+ options: [
81
+ { label: '技术部', value: '技术部' },
82
+ { label: '产品部', value: '产品部' },
83
+ { label: '设计部', value: '设计部' },
84
+ { label: '市场部', value: '市场部' },
85
+ { label: '人事部', value: '人事部' },
86
+ ],
87
+ },
88
+ { prop: 'position', label: '职位', type: 'input' as const, required: true },
89
+ { prop: 'salary', label: '薪资', type: 'number' as const, required: true, min: 0 },
90
+ { prop: 'status', label: '状态', type: 'switch' as const, activeText: '在职', inactiveText: '离职' },
91
+ ]
92
+
93
+ const rules: FormRules = {
94
+ name: [{ required: true, message: '请输入姓名', trigger: 'blur' }],
95
+ department: [{ required: true, message: '请选择部门', trigger: 'change' }],
96
+ position: [{ required: true, message: '请输入职位', trigger: 'blur' }],
97
+ salary: [{ required: true, message: '请输入薪资', trigger: 'blur' }],
98
+ }
99
+
100
+ const loading = ref(false)
101
+ const total = ref(0)
102
+ const dialogVisible = ref(false)
103
+ const currentRow = ref<Record<string, any>>({})
104
+
105
+ const query = reactive({
106
+ page: 1,
107
+ pageSize: 10,
108
+ })
109
+
110
+ const allData = ref<Employee[]>([
111
+ { id: 1, name: '陈伟', department: '技术部', position: '前端工程师', salary: 18000, status: true },
112
+ { id: 2, name: '刘芳', department: '产品部', position: '产品经理', salary: 22000, status: true },
113
+ { id: 3, name: '张磊', department: '技术部', position: '后端工程师', salary: 20000, status: true },
114
+ { id: 4, name: '王丽', department: '设计部', position: 'UI设计师', salary: 16000, status: true },
115
+ { id: 5, name: '李强', department: '市场部', position: '市场总监', salary: 25000, status: true },
116
+ { id: 6, name: '赵敏', department: '人事部', position: 'HR经理', salary: 17000, status: false },
117
+ { id: 7, name: '孙涛', department: '技术部', position: '架构师', salary: 35000, status: true },
118
+ { id: 8, name: '周婷', department: '产品部', position: '产品助理', salary: 10000, status: true },
119
+ { id: 9, name: '吴凯', department: '技术部', position: '测试工程师', salary: 14000, status: false },
120
+ { id: 10, name: '郑华', department: '设计部', position: '视觉设计师', salary: 15000, status: true },
121
+ { id: 11, name: '冯雪', department: '市场部', position: '运营专员', salary: 12000, status: true },
122
+ { id: 12, name: '杨帆', department: '技术部', position: 'DevOps工程师', salary: 23000, status: true },
123
+ ])
124
+
125
+ const tableData = ref<Employee[]>([])
126
+
127
+ let nextId = 13
128
+
129
+ const deptTagType = (dept: string) => {
130
+ const map: Record<string, string> = {
131
+ '技术部': 'primary',
132
+ '产品部': 'warning',
133
+ '设计部': 'success',
134
+ '市场部': 'danger',
135
+ '人事部': 'info',
136
+ }
137
+ return map[dept] || 'info'
138
+ }
139
+
140
+ const getList = () => {
141
+ loading.value = true
142
+ setTimeout(() => {
143
+ const start = (query.page - 1) * query.pageSize
144
+ const end = start + query.pageSize
145
+ tableData.value = allData.value.slice(start, end)
146
+ total.value = allData.value.length
147
+ loading.value = false
148
+ }, 300)
149
+ }
150
+
151
+ const handlePaginationChange = () => {
152
+ getList()
153
+ }
154
+
155
+ const handleAdd = () => {
156
+ currentRow.value = {}
157
+ dialogVisible.value = true
158
+ }
159
+
160
+ const handleEdit = (row: Employee) => {
161
+ currentRow.value = { ...row }
162
+ dialogVisible.value = true
163
+ }
164
+
165
+ const handleFormConfirm = (data: Record<string, any>) => {
166
+ if (data.id) {
167
+ const index = allData.value.findIndex(item => item.id === data.id)
168
+ if (index !== -1) {
169
+ allData.value[index] = { ...allData.value[index], ...data }
170
+ }
171
+ ElMessage.success('编辑成功')
172
+ } else {
173
+ allData.value.unshift({
174
+ id: nextId++,
175
+ name: data.name,
176
+ department: data.department,
177
+ position: data.position,
178
+ salary: data.salary,
179
+ status: data.status ?? true,
180
+ })
181
+ ElMessage.success('新增成功')
182
+ }
183
+ getList()
184
+ }
185
+
186
+ const handleDelete = (row: Employee) => {
187
+ ElMessageBox.confirm(`确认删除员工 ${row.name}?`, '提示', {
188
+ confirmButtonText: '确认',
189
+ cancelButtonText: '取消',
190
+ type: 'warning',
191
+ }).then(() => {
192
+ const index = allData.value.findIndex(item => item.id === row.id)
193
+ if (index !== -1) {
194
+ allData.value.splice(index, 1)
195
+ }
196
+ getList()
197
+ ElMessage.success('删除成功')
198
+ }).catch(() => {})
199
+ }
200
+
201
+ onMounted(() => {
202
+ getList()
203
+ })
204
+ </script>
@@ -22,6 +22,7 @@
22
22
  :label="col.label"
23
23
  :width="col.width"
24
24
  :min-width="col.minWidth"
25
+ :visible="col.visible"
25
26
  >
26
27
  <template v-if="col.slot" #default="{ row }">
27
28
  <el-tag
@@ -56,11 +57,12 @@ interface ColumnConfig {
56
57
  width?: number | string
57
58
  minWidth?: number | string
58
59
  slot?: boolean
60
+ visible?: boolean
59
61
  }
60
62
 
61
63
  const columns: ColumnConfig[] = [
62
64
  { prop: 'id', label: 'ID', width: 80 },
63
- { prop: 'name', label: '商品名称', minWidth: 150 },
65
+ { prop: 'name', label: '商品名称', minWidth: 150, visible: false },
64
66
  { prop: 'category', label: '分类', width: 120, slot: true },
65
67
  { prop: 'price', label: '价格', width: 100 },
66
68
  { prop: 'stock', label: '库存', width: 80 },
@@ -0,0 +1,57 @@
1
+ <template>
2
+ <el-card shadow="hover">
3
+ <template #header>
4
+ <span>千分位输入演示</span>
5
+ </template>
6
+
7
+ <el-form :model="form" label-width="120px" style="max-width: 600px">
8
+ <el-form-item label="金额">
9
+ <ElInputThousandth v-model="form.amount" placeholder="请输入金额" />
10
+ </el-form-item>
11
+
12
+ <el-form-item label="价格">
13
+ <ElInputThousandth v-model="form.price" placeholder="请输入价格" />
14
+ </el-form-item>
15
+
16
+ <el-form-item label="数量">
17
+ <el-input-number v-model="form.quantity" :min="1" />
18
+ </el-form-item>
19
+
20
+ <el-form-item>
21
+ <el-button type="primary" @click="handleSubmit">提交</el-button>
22
+ <el-button @click="handleReset">重置</el-button>
23
+ </el-form-item>
24
+ </el-form>
25
+
26
+ <el-divider />
27
+
28
+ <el-descriptions title="表单数据" :column="1" border>
29
+ <el-descriptions-item label="金额">{{ form.amount }}</el-descriptions-item>
30
+ <el-descriptions-item label="价格">{{ form.price }}</el-descriptions-item>
31
+ <el-descriptions-item label="数量">{{ form.quantity }}</el-descriptions-item>
32
+ </el-descriptions>
33
+ </el-card>
34
+ </template>
35
+
36
+ <script setup lang="ts">
37
+ import { reactive } from 'vue'
38
+ import { ElMessage } from 'element-plus'
39
+ import ElInputThousandth from '@/components/ElInputThousandth.vue'
40
+
41
+ const form = reactive({
42
+ amount: undefined as number | undefined,
43
+ price: undefined as number | undefined,
44
+ quantity: 1,
45
+ })
46
+
47
+ const handleSubmit = () => {
48
+ ElMessage.success('提交成功!')
49
+ console.log('form data:', { ...form })
50
+ }
51
+
52
+ const handleReset = () => {
53
+ form.amount = undefined
54
+ form.price = undefined
55
+ form.quantity = 1
56
+ }
57
+ </script>
@@ -1,8 +0,0 @@
1
- {
2
- "explorer.fileNesting.enabled": true,
3
- "explorer.fileNesting.patterns": {
4
- "tsconfig.json": "tsconfig.*.json, env.d.ts, typed-router.d.ts",
5
- "vite.config.*": "jsconfig*, vitest.config.*, cypress.config.*, playwright.config.*",
6
- "package.json": "package-lock.json, pnpm*, .yarnrc*, yarn*, .eslint*, eslint*, .oxlint*, oxlint*, .oxfmt*, .prettier*, prettier*, .editorconfig"
7
- }
8
- }