vue2server7 7.0.107 → 7.0.109

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.
@@ -0,0 +1,74 @@
1
+ ---
2
+ name: 表格列配置演示页面设计
3
+ description: 新增表格列配置演示页面的设计文档
4
+ type: project
5
+ ---
6
+
7
+ # 表格列配置演示页面设计
8
+
9
+ ## 概述
10
+
11
+ 为前端项目新增一个"表格列配置演示"路由页面,提供基础的表格页面框架,预留列配置功能的扩展点。
12
+
13
+ ## 功能需求
14
+
15
+ 1. **基础表格页面**:包含标题、工具栏、表格组件、分页
16
+ 2. **模拟数据**:提供基础的表格数据和列配置数据结构
17
+ 3. **预留扩展点**:列设置按钮、预留列配置功能位置
18
+ 4. **路由配置**:在侧边栏菜单中显示"表格列配置演示"
19
+
20
+ ## 架构设计
21
+
22
+ ### 文件结构
23
+
24
+ ```
25
+ frontEnd/src/
26
+ ├── pages/
27
+ │ └── ColumnConfigDemoPage.vue # 新增:列配置演示页面
28
+ └── router/
29
+ └── routes.js # 修改:添加新路由
30
+ ```
31
+
32
+ ### 技术栈
33
+
34
+ - Vue 3 + Composition API + TypeScript
35
+ - Element Plus UI 组件库
36
+ - vue-router 4.x
37
+
38
+ ## 实现步骤
39
+
40
+ 1. 创建 `ColumnConfigDemoPage.vue` 页面文件
41
+ - 基础页面结构(标题、工具栏、el-table、分页)
42
+ - 模拟列配置数据(含 key、label、width、align 等属性)
43
+ - 模拟表格数据
44
+ - 预留"列设置"按钮及相关 UI 位置
45
+
46
+ 2. 修改 `routes.js` 添加新路由
47
+ - 路径:/column-config-demo
48
+ - 名称:ColumnConfigDemo
49
+ - 菜单标题:表格列配置演示
50
+ - showInMenu: true
51
+
52
+ ## 数据结构
53
+
54
+ ### 列配置接口
55
+
56
+ ```typescript
57
+ interface ColumnConfig {
58
+ key: string // 列唯一标识
59
+ prop: string // 数据字段名
60
+ label: string // 列标题
61
+ width?: number // 列宽
62
+ minWidth?: number // 最小列宽
63
+ align?: 'left' | 'center' | 'right'
64
+ fixed?: boolean | 'left' | 'right'
65
+ sortable?: boolean | 'custom'
66
+ visible?: boolean // 是否显示
67
+ }
68
+ ```
69
+
70
+ ## 注意事项
71
+
72
+ - 遵循现有页面的代码风格和目录结构
73
+ - 列配置的具体实现逻辑由用户自行开发
74
+ - 页面结构保持简洁,便于后续扩展
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: 表格列配置组件设计
3
+ description: 集成 TableColumnSettings 列配置组件到项目
4
+ type: project
5
+ ---
6
+
7
+ # 表格列配置组件设计
8
+
9
+ ## 概述
10
+
11
+ 将用户提供的 TableColumnSettings 列配置组件集成到项目中,并更新 ColumnConfigDemoPage 演示页面使用该组件。
12
+
13
+ ## 功能需求
14
+
15
+ 1. **TableColumnSettings 组件**
16
+ - 弹窗形式的列配置面板
17
+ - 支持全选/反选、多选框组
18
+ - 支持禁用某些列(不可取消选中)
19
+ - 确认后触发事件返回选中的列 keys
20
+ - 自定义按钮文本
21
+
22
+ 2. **演示页面集成**
23
+ - 在 ColumnConfigDemoPage 中使用列配置组件
24
+ - 实现列显示/隐藏的动态更新
25
+ - 支持强制显示的列(disabled)
26
+
27
+ ## 架构设计
28
+
29
+ ### 文件结构
30
+
31
+ ```
32
+ frontEnd/src/
33
+ ├── components/
34
+ │ └── TableColumnSettings.vue # 新增:列配置组件
35
+ └── pages/
36
+ └── ColumnConfigDemoPage.vue # 修改:集成组件
37
+ ```
38
+
39
+ ## 组件接口
40
+
41
+ ### Props
42
+
43
+ | 名称 | 类型 | 说明 | 默认值 |
44
+ |------|------|------|--------|
45
+ | columns | Array | 列配置数组,每项需包含 prop、label、disabled | [] |
46
+ | selectedKeys | Array | 当前选中的列 key 数组 | [] |
47
+ | buttonText | String | 按钮显示文本 | '列配置' |
48
+
49
+ ### Events
50
+
51
+ | 名称 | 参数 | 说明 |
52
+ |------|------|------|
53
+ | confirm | selectedKeys | 用户点击确认时触发,返回选中的列 keys 数组 |
54
+
55
+ ### Column 数据结构
56
+
57
+ ```javascript
58
+ {
59
+ prop: 'name', // 列唯一标识
60
+ label: '姓名', // 列显示名称
61
+ disabled: false // 是否禁用(禁用的列始终显示,不可取消选中)
62
+ }
63
+ ```
64
+
65
+ ## 实现步骤
66
+
67
+ 1. 创建 `TableColumnSettings.vue` 组件
68
+ 2. 更新 `ColumnConfigDemoPage.vue` 集成组件
69
+ 3. 验证功能正常工作
@@ -0,0 +1,296 @@
1
+ <template>
2
+ <!-- 列配置触发按钮 -->
3
+ <el-button @click="visible = true">
4
+ <el-icon><Setting /></el-icon>
5
+ 列配置
6
+ </el-button>
7
+
8
+ <!-- 列配置弹窗 -->
9
+ <el-dialog
10
+ v-model="visible"
11
+ title="表格列配置"
12
+ width="760px"
13
+ append-to-body
14
+ >
15
+ <!-- 提示文字 -->
16
+ <div class="desc">请选择需要在表格中显示的数据列</div>
17
+
18
+ <!-- 列选择容器 -->
19
+ <div class="column-box">
20
+ <!-- 全选行 -->
21
+ <div class="check-all-row">
22
+ <el-checkbox
23
+ v-model="checkAll"
24
+ :indeterminate="isIndeterminate"
25
+ @change="handleCheckAllChange"
26
+ >
27
+ 全选
28
+ </el-checkbox>
29
+ </div>
30
+
31
+ <!-- 列多选组 -->
32
+ <el-checkbox-group
33
+ v-model="checkedKeys"
34
+ class="column-list"
35
+ @change="updateCheckAllStatus"
36
+ >
37
+ <el-checkbox
38
+ v-for="item in columns"
39
+ :key="item.prop"
40
+ :label="item.prop"
41
+ :disabled="item.disabled"
42
+ >
43
+ {{ item.label }}
44
+ </el-checkbox>
45
+ </el-checkbox-group>
46
+ </div>
47
+
48
+ <!-- 弹窗底部按钮 -->
49
+ <template #footer>
50
+ <el-button @click="visible = false">取消</el-button>
51
+ <el-button type="primary" @click="handleConfirm">
52
+ 确认
53
+ </el-button>
54
+ </template>
55
+ </el-dialog>
56
+ </template>
57
+
58
+ <script setup>
59
+ // 导入 Vue 组合式 API
60
+ import { ref, computed, watch } from 'vue'
61
+ // 导入 Element Plus 图标
62
+ import { Setting } from '@element-plus/icons-vue'
63
+
64
+ /**
65
+ * 组件 Props 定义
66
+ */
67
+ const props = defineProps({
68
+ /**
69
+ * 列配置数组
70
+ * 每个对象包含:
71
+ * - prop: 列唯一标识(对应表格列的 prop)
72
+ * - label: 列显示名称
73
+ * - disabled: 是否禁用(禁用的列始终显示,不可取消选中)
74
+ */
75
+ columns: {
76
+ type: Array,
77
+ default: () => []
78
+ },
79
+ /**
80
+ * 当前选中的列 key 数组
81
+ * 用于外部控制初始选中状态
82
+ */
83
+ selectedKeys: {
84
+ type: Array,
85
+ default: () => []
86
+ },
87
+ /**
88
+ * 按钮显示文本
89
+ * 可自定义按钮文字,默认"列配置"
90
+ */
91
+ buttonText: {
92
+ type: String,
93
+ default: '列配置'
94
+ },
95
+ /**
96
+ * localStorage 存储键名
97
+ * 设置后会自动将列配置持久化到浏览器本地存储
98
+ * 刷新页面后配置自动恢复
99
+ */
100
+ storageKey: {
101
+ type: String,
102
+ default: ''
103
+ }
104
+ })
105
+
106
+ /**
107
+ * 组件 Events 定义
108
+ * confirm: 用户点击确认按钮时触发,参数为选中的列 key 数组
109
+ */
110
+ const emit = defineEmits(['confirm'])
111
+
112
+ // ==================== 响应式状态 ====================
113
+
114
+ /**
115
+ * 弹窗显示/隐藏状态
116
+ * true: 弹窗打开
117
+ * false: 弹窗关闭
118
+ */
119
+ const visible = ref(false)
120
+
121
+ /**
122
+ * 当前选中的列 key 数组
123
+ * 存储用户在弹窗中勾选的列 prop
124
+ */
125
+ const checkedKeys = ref([])
126
+
127
+ /**
128
+ * 全选复选框状态
129
+ * true: 全部选中
130
+ * false: 全部未选中
131
+ */
132
+ const checkAll = ref(false)
133
+
134
+ /**
135
+ * 半选状态标识
136
+ * true: 部分选中(显示减号)
137
+ * false: 全选或全不选
138
+ */
139
+ const isIndeterminate = ref(false)
140
+
141
+ // ==================== 计算属性 ====================
142
+
143
+ /**
144
+ * 可操作的列 key 数组
145
+ * 过滤掉 disabled = true 的列
146
+ * 这些列可以被用户自由勾选/取消
147
+ */
148
+ const enabledKeys = computed(() => {
149
+ return props.columns
150
+ .filter(item => !item.disabled)
151
+ .map(item => item.prop)
152
+ })
153
+
154
+ // ==================== 监听器 ====================
155
+
156
+ /**
157
+ * 监听弹窗打开状态
158
+ * 弹窗打开时:
159
+ * 1. 优先从 localStorage 读取保存的配置
160
+ * 2. 没有存储则使用外部传入的 selectedKeys
161
+ * 3. 更新全选/半选状态
162
+ */
163
+ watch(visible, val => {
164
+ if (val) {
165
+ // 优先从 localStorage 读取,其次使用 props
166
+ if (props.storageKey) {
167
+ const stored = localStorage.getItem(props.storageKey)
168
+ if (stored) {
169
+ try {
170
+ checkedKeys.value = JSON.parse(stored)
171
+ } catch {
172
+ // JSON 解析失败时回退到默认值
173
+ checkedKeys.value = [...props.selectedKeys]
174
+ }
175
+ } else {
176
+ checkedKeys.value = [...props.selectedKeys]
177
+ }
178
+ } else {
179
+ checkedKeys.value = [...props.selectedKeys]
180
+ }
181
+ // 更新全选复选框状态
182
+ updateCheckAllStatus()
183
+ }
184
+ })
185
+
186
+ // ==================== 方法 ====================
187
+
188
+ /**
189
+ * 全选/取消全选处理函数
190
+ * @param {boolean} val - 全选框当前值
191
+ *
192
+ * 逻辑说明:
193
+ * - 全选时:所有可操作列 + 已选中的禁用列 都被选中
194
+ * - 取消全选时:只保留禁用列中已选中的项
195
+ */
196
+ function handleCheckAllChange(val) {
197
+ // 找出禁用且已选中的列(这些列必须始终保持选中)
198
+ const disabledCheckedKeys = props.columns
199
+ .filter(item => item.disabled && checkedKeys.value.includes(item.prop))
200
+ .map(item => item.prop)
201
+
202
+ checkedKeys.value = val
203
+ // 全选:禁用的已选中项 + 所有可操作项
204
+ ? [...new Set([...disabledCheckedKeys, ...enabledKeys.value])]
205
+ // 取消全选:只保留禁用的已选中项
206
+ : disabledCheckedKeys
207
+
208
+ // 更新全选和半选状态
209
+ updateCheckAllStatus()
210
+ }
211
+
212
+ /**
213
+ * 更新全选和半选状态
214
+ *
215
+ * 计算逻辑:
216
+ * 1. 统计可操作列中已选中的数量
217
+ * 2. 数量 = 总数 → 全选
218
+ * 3. 0 < 数量 < 总数 → 半选
219
+ * 4. 数量 = 0 → 全不选
220
+ */
221
+ function updateCheckAllStatus() {
222
+ // 统计可操作列中已选中的数量
223
+ const count = checkedKeys.value.filter(key =>
224
+ enabledKeys.value.includes(key)
225
+ ).length
226
+
227
+ // 设置全选状态
228
+ checkAll.value = count === enabledKeys.value.length
229
+ // 设置半选状态
230
+ isIndeterminate.value = count > 0 && count < enabledKeys.value.length
231
+ }
232
+
233
+ /**
234
+ * 确认按钮处理函数
235
+ *
236
+ * 逻辑:
237
+ * 1. 如果设置了 storageKey,将配置保存到 localStorage
238
+ * 2. 触发 confirm 事件,通知父组件
239
+ * 3. 关闭弹窗
240
+ */
241
+ function handleConfirm() {
242
+ // 保存到 localStorage 持久化
243
+ if (props.storageKey) {
244
+ localStorage.setItem(props.storageKey, JSON.stringify(checkedKeys.value))
245
+ }
246
+ // 通知父组件选中结果
247
+ emit('confirm', checkedKeys.value)
248
+ // 关闭弹窗
249
+ visible.value = false
250
+ }
251
+ </script>
252
+
253
+ <style scoped>
254
+ /**
255
+ * 提示文字样式
256
+ */
257
+ .desc {
258
+ margin-bottom: 22px;
259
+ color: #666;
260
+ font-size: 16px;
261
+ }
262
+
263
+ /**
264
+ * 列选择容器边框
265
+ */
266
+ .column-box {
267
+ border: 1px solid #dcdfe6;
268
+ }
269
+
270
+ /**
271
+ * 全选行样式
272
+ */
273
+ .check-all-row {
274
+ padding: 18px 24px;
275
+ border-bottom: 1px solid #dcdfe6;
276
+ }
277
+
278
+ /**
279
+ * 列列表样式
280
+ * flex 布局自动换行
281
+ */
282
+ .column-list {
283
+ padding: 22px 24px;
284
+ display: flex;
285
+ flex-wrap: wrap;
286
+ gap: 22px 28px;
287
+ }
288
+
289
+ /**
290
+ * 覆盖 Element Plus 默认复选框右边距
291
+ * 因为使用 gap 控制间距,不需要默认的 margin-right
292
+ */
293
+ :deep(.el-checkbox) {
294
+ margin-right: 0;
295
+ }
296
+ </style>
@@ -0,0 +1,166 @@
1
+ <template>
2
+ <section class="page column-config-demo-page">
3
+ <h1 class="title">表格列配置演示</h1>
4
+
5
+ <div class="toolbar">
6
+ <el-button type="primary">查询</el-button>
7
+ <el-button>重置</el-button>
8
+
9
+ <!-- 列配置组件 -->
10
+ <TableColumnSettings
11
+ :columns="columnSettings"
12
+ :selected-keys="selectedKeys"
13
+ storage-key="column-config-demo"
14
+ @confirm="onColumnConfirm"
15
+ />
16
+ </div>
17
+
18
+ <el-table
19
+ :key="tableKey"
20
+ :data="tableData"
21
+ border
22
+ stripe
23
+ size="small"
24
+ style="width: 100%"
25
+ >
26
+ <el-table-column type="index" label="序号" width="60" align="center" />
27
+ <el-table-column
28
+ v-for="col in visibleColumns"
29
+ :key="col.prop"
30
+ :prop="col.prop"
31
+ :label="col.label"
32
+ :width="col.width"
33
+ :min-width="col.minWidth"
34
+ :align="col.align || 'left'"
35
+ :fixed="col.fixed || false"
36
+ >
37
+ </el-table-column>
38
+ </el-table>
39
+
40
+ <div class="pagination">
41
+ <el-pagination
42
+ layout="total, prev, pager, next"
43
+ :current-page="page"
44
+ :page-size="pageSize"
45
+ :total="total"
46
+ @current-change="onPageChange"
47
+ />
48
+ </div>
49
+ </section>
50
+ </template>
51
+
52
+ <script setup lang="ts">
53
+ import { ref, reactive, computed, onMounted } from 'vue'
54
+ import TableColumnSettings from '../components/TableColumnSettings.vue'
55
+
56
+ // 表格数据接口
57
+ interface TableRow {
58
+ id: number
59
+ name: string
60
+ department: string
61
+ position: string
62
+ phone: string
63
+ email: string
64
+ joinDate: string
65
+ status: string
66
+ }
67
+
68
+ // 响应式数据
69
+ const tableKey = ref(0)
70
+ const page = ref(1)
71
+ const pageSize = ref(10)
72
+ const total = ref(50)
73
+ const tableData = ref<TableRow[]>([])
74
+
75
+ // 列配置 - 用于表格渲染
76
+ const columns = reactive([
77
+ { key: 'name', prop: 'name', label: '姓名', width: 100 },
78
+ { key: 'department', prop: 'department', label: '部门', width: 120 },
79
+ { key: 'position', prop: 'position', label: '职位', width: 120 },
80
+ { key: 'phone', prop: 'phone', label: '电话', width: 140 },
81
+ { key: 'email', prop: 'email', label: '邮箱', minWidth: 180 },
82
+ { key: 'joinDate', prop: 'joinDate', label: '入职日期', width: 120, align: 'center' },
83
+ { key: 'status', prop: 'status', label: '状态', width: 100, align: 'center' }
84
+ ])
85
+
86
+ // 列配置 - 用于列配置组件
87
+ const columnSettings = computed(() => {
88
+ return columns.map(col => ({
89
+ prop: col.prop,
90
+ label: col.label,
91
+ disabled: col.prop === 'name' // 姓名列强制显示,不可取消
92
+ }))
93
+ })
94
+
95
+ // 当前选中的列
96
+ const selectedKeys = ref<string[]>([])
97
+
98
+ // 可见列
99
+ const visibleColumns = computed(() => {
100
+ return columns.filter(col => selectedKeys.value.includes(col.prop))
101
+ })
102
+
103
+ // 列配置确认
104
+ const onColumnConfirm = (keys: string[]) => {
105
+ selectedKeys.value = keys
106
+ tableKey.value += 1 // 强制刷新表格
107
+ }
108
+
109
+ // 分页切换
110
+ const onPageChange = (p: number) => {
111
+ page.value = p
112
+ loadData()
113
+ }
114
+
115
+ // 加载模拟数据
116
+ const loadData = () => {
117
+ const data: TableRow[] = []
118
+ for (let i = 0; i < pageSize.value; i++) {
119
+ const index = (page.value - 1) * pageSize.value + i + 1
120
+ data.push({
121
+ id: index,
122
+ name: `员工${index}`,
123
+ department: ['技术部', '产品部', '运营部', '市场部'][index % 4],
124
+ position: ['开发工程师', '产品经理', '运营专员', '市场专员'][index % 4],
125
+ phone: `138${String(index).padStart(8, '0')}`,
126
+ email: `employee${index}@example.com`,
127
+ joinDate: `2024-${String((index % 12) + 1).padStart(2, '0')}-${String((index % 28) + 1).padStart(2, '0')}`,
128
+ status: ['在职', '离职', '休假'][index % 3]
129
+ })
130
+ }
131
+ tableData.value = data
132
+ }
133
+
134
+ onMounted(() => {
135
+ // 从 localStorage 读取配置,没有则默认全选
136
+ const storageKey = 'column-config-demo'
137
+ const stored = localStorage.getItem(storageKey)
138
+ if (stored) {
139
+ try {
140
+ selectedKeys.value = JSON.parse(stored)
141
+ } catch {
142
+ selectedKeys.value = columns.map(col => col.prop)
143
+ }
144
+ } else {
145
+ selectedKeys.value = columns.map(col => col.prop)
146
+ }
147
+ loadData()
148
+ })
149
+ </script>
150
+
151
+ <style scoped>
152
+ .page.column-config-demo-page {
153
+ padding: 16px;
154
+ }
155
+ .title {
156
+ font-size: 18px;
157
+ margin-bottom: 12px;
158
+ }
159
+ .toolbar {
160
+ margin-bottom: 12px;
161
+ }
162
+ .pagination {
163
+ margin-top: 12px;
164
+ text-align: right;
165
+ }
166
+ </style>
@@ -6,6 +6,7 @@ import PositionReportPage from '../pages/PositionReportPage.vue'
6
6
  import DateRangePage from '../pages/DateRangePage.vue'
7
7
  import OrgTreeSelectPage from '../pages/OrgTreeSelectPage.vue'
8
8
  import MoneyInputPage from '../pages/MoneyInputPage.vue'
9
+ import ColumnConfigDemoPage from '../pages/ColumnConfigDemoPage.vue'
9
10
 
10
11
  export const routes = [
11
12
  {
@@ -83,5 +84,14 @@ export const routes = [
83
84
  title: '金额输入',
84
85
  showInMenu: true
85
86
  }
87
+ },
88
+ {
89
+ path: '/column-config-demo',
90
+ name: 'ColumnConfigDemo',
91
+ component: ColumnConfigDemoPage,
92
+ meta: {
93
+ title: '表格列配置演示',
94
+ showInMenu: true
95
+ }
86
96
  }
87
97
  ]
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vue2server7",
3
- "version": "7.0.107",
3
+ "version": "7.0.109",
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,39 +0,0 @@
1
- // src/utils/configWatcher.js
2
-
3
- const CONFIG_VERSION_KEY = 'APP_CONFIG_VERSION'
4
-
5
- function getConfigVersionFromText(text) {
6
- const match = text.match(/VERSION:\s*['"](.+?)['"]/)
7
- return match?.[1]
8
- }
9
-
10
- export function startConfigWatcher(interval = 10000) {
11
- setInterval(async () => {
12
- try {
13
- const res = await fetch(`/config.js?t=${Date.now()}`, {
14
- cache: 'no-store'
15
- })
16
-
17
- const text = await res.text()
18
- const latestVersion = getConfigVersionFromText(text)
19
-
20
- if (!latestVersion) return
21
-
22
- const localVersion = localStorage.getItem(CONFIG_VERSION_KEY)
23
-
24
- // 第一次没有版本号,先存起来
25
- if (!localVersion) {
26
- localStorage.setItem(CONFIG_VERSION_KEY, latestVersion)
27
- return
28
- }
29
-
30
- // 版本不一致,更新本地版本并刷新页面
31
- if (latestVersion !== localVersion) {
32
- localStorage.setItem(CONFIG_VERSION_KEY, latestVersion)
33
- window.location.reload()
34
- }
35
- } catch (err) {
36
- console.error('配置版本检查失败:', err)
37
- }
38
- }, interval)
39
- }