t20-common-lib 0.10.9 → 0.11.0

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": "t20-common-lib",
3
- "version": "0.10.9",
3
+ "version": "0.11.0",
4
4
  "description": "T20",
5
5
  "private": false,
6
6
  "main": "dist/index.js",
@@ -3,7 +3,7 @@
3
3
  <DraggableFab
4
4
  v-if="showConfigBtn"
5
5
  title="配置显示列"
6
- @click="$emit('open-config')"
6
+ @click="showColumnConfig"
7
7
  />
8
8
 
9
9
  <div v-for="panel in panels" :key="panel.key">
@@ -37,16 +37,28 @@
37
37
  </template>
38
38
  </N20-expandable-pane>
39
39
  </div>
40
+ <DynamicDescriptionsConfig
41
+ ref="showColumn"
42
+ :dialogVisible.sync="showColumnVisible"
43
+ :check-columns="checkedColumns"
44
+ :columns="allColumns"
45
+ :columns-groups="columnGroups"
46
+ :auto-save="!!pageId"
47
+ :page-id="pageId"
48
+ @setColumns="handleSetColumns"
49
+ />
40
50
  </div>
41
51
  </template>
42
52
 
43
53
  <script>
44
54
  import DraggableFab from '../../draggable-fab/src/main.vue'
55
+ import DynamicDescriptionsConfig from '../../dynamic-descriptions-config/src/main.vue'
45
56
 
46
57
  export default {
47
58
  name: 'DynamicDescriptions',
48
59
  components: {
49
- DraggableFab
60
+ DraggableFab,
61
+ DynamicDescriptionsConfig
50
62
  },
51
63
  props: {
52
64
  panels: {
@@ -60,6 +72,98 @@ export default {
60
72
  showConfigBtn: {
61
73
  type: Boolean,
62
74
  default: true
75
+ },
76
+ pageId: {
77
+ type: String,
78
+ default: ''
79
+ }
80
+ },
81
+ data() {
82
+ return {
83
+ showColumnVisible: false,
84
+ checkedColumns: []
85
+ }
86
+ },
87
+ created() {
88
+ this.checkedColumns = this.allColumns || []
89
+ },
90
+ mounted() {
91
+ if (this.pageId) {
92
+ this.getColumns()
93
+ }
94
+ },
95
+ computed: {
96
+ columnGroups() {
97
+ return this.panels.map(p => ({
98
+ groupId: p.key,
99
+ groupName: p.title,
100
+ type: p.type
101
+ }))
102
+ },
103
+ allColumns() {
104
+ let cols = []
105
+ this.panels.forEach(panel => {
106
+ const items = panel.fields || panel.columns || []
107
+ items.forEach(item => {
108
+ let isVisible = true
109
+ if (typeof item.visible === 'function') {
110
+ isVisible = item.visible(this.formData)
111
+ } else if (typeof item.visible === 'boolean') {
112
+ isVisible = item.visible
113
+ }
114
+
115
+ if (isVisible) {
116
+ cols.push({
117
+ ...item,
118
+ groupId: panel.key,
119
+ sourceGroup: panel.key,
120
+ configLabel: item.label
121
+ })
122
+ }
123
+ })
124
+ })
125
+ return cols
126
+ },
127
+ processedFormData() {
128
+ const newFormData = JSON.parse(JSON.stringify(this.formData))
129
+
130
+ // Ensure target groups exist in newFormData for object types
131
+ this.displayPanels.forEach(p => {
132
+ if (p.type === 'object' && !newFormData[p.key]) {
133
+ newFormData[p.key] = {}
134
+ }
135
+ })
136
+
137
+ this.checkedColumns.forEach(col => {
138
+ // Only handle object fields that have moved to a different group
139
+ if (col.sourceGroup && col.groupId && col.sourceGroup !== col.groupId) {
140
+ // We assume sourceGroup refers to a key in formData
141
+ if (this.formData[col.sourceGroup]) {
142
+ const val = this.formData[col.sourceGroup][col.prop]
143
+ // Assign to the new location so the component can find it
144
+ if (newFormData[col.groupId]) {
145
+ newFormData[col.groupId][col.prop] = val
146
+ }
147
+ }
148
+ }
149
+ })
150
+
151
+ return newFormData
152
+ },
153
+ displayPanels() {
154
+ return this.panels.map(panel => {
155
+ const panelItems = this.checkedColumns.filter(col => col.groupId === panel.key)
156
+
157
+ if (panelItems.length === 0) return null
158
+
159
+ const newPanel = { ...panel }
160
+ if (panel.type === 'object') {
161
+ newPanel.fields = panelItems
162
+ } else {
163
+ newPanel.columns = panelItems
164
+ }
165
+ return newPanel
166
+ }).filter(p => p !== null)
63
167
  }
64
168
  },
65
169
  methods: {
@@ -215,6 +319,24 @@ export default {
215
319
  window.removeEventListener('mousemove', this.onDrag)
216
320
  window.removeEventListener('mouseup', this.stopDrag)
217
321
  },
322
+ getColumns() {
323
+ this.$refs.showColumn.getColumns().then((list) => {
324
+ if (list && list.length > 0) {
325
+ this.checkedColumns = list
326
+ } else {
327
+ this.checkedColumns = this.allColumns
328
+ }
329
+ })
330
+ },
331
+ showColumnConfig() {
332
+ if (!this.checkedColumns || this.checkedColumns.length === 0) {
333
+ this.checkedColumns = [...this.allColumns]
334
+ }
335
+ this.showColumnVisible = true
336
+ },
337
+ handleSetColumns(newColumns) {
338
+ this.checkedColumns = newColumns
339
+ }
218
340
  }
219
341
  }
220
342
  </script>
@@ -0,0 +1,8 @@
1
+ import DynamicDescriptionsConfig from './src/main';
2
+
3
+ /* istanbul ignore next */
4
+ DynamicDescriptionsConfig.install = function(Vue) {
5
+ Vue.component(DynamicDescriptionsConfig.name, DynamicDescriptionsConfig);
6
+ };
7
+
8
+ export default DynamicDescriptionsConfig;
@@ -0,0 +1,474 @@
1
+ <template>
2
+ <el-dialog
3
+ :visible.sync="visible"
4
+ :title="title"
5
+ width="900px"
6
+ :close-on-click-modal="false"
7
+ append-to-body
8
+ custom-class="local-show-column-dialog"
9
+ >
10
+ <div class="dialog-content">
11
+ <!-- Left Side: Checkboxes -->
12
+ <div class="left-panel">
13
+ <div class="panel-header">
14
+ <span>可选字段</span>
15
+ <el-link type="primary" :underline="false" @click="selectAll" class="header-action">全选</el-link>
16
+ </div>
17
+ <div class="panel-body">
18
+ <div v-for="group in columnsGroups" :key="group.groupId" class="group-section">
19
+ <div class="group-title">{{ group.groupName }}</div>
20
+ <div class="group-items">
21
+ <el-checkbox
22
+ v-for="col in getGroupColumns(group.groupId)"
23
+ :key="col.prop"
24
+ :label="col.label"
25
+ :value="isChecked(col)"
26
+ @change="(val) => handleCheckChange(val, col)"
27
+ >
28
+ {{ col.label }}
29
+ </el-checkbox>
30
+ </div>
31
+ </div>
32
+ </div>
33
+ </div>
34
+
35
+ <!-- Right Side: Draggable Hierarchy -->
36
+ <div class="right-panel">
37
+ <div class="panel-header">
38
+ <span>当前选定项 (可拖拽排序)</span>
39
+ <el-link type="primary" :underline="false" @click="clearAll" class="header-action">清空</el-link>
40
+ </div>
41
+ <div class="panel-body">
42
+ <div v-for="group in localGroups" :key="group.groupId" class="drag-group-section">
43
+ <div class="group-title-bar">
44
+ <span class="font-bold">{{ group.groupName }}</span>
45
+ <span class="count-badge">{{ group.children.length }}</span>
46
+ </div>
47
+
48
+ <draggable
49
+ v-model="group.children"
50
+ :group="getDragGroup(group)"
51
+ animation="200"
52
+ class="drag-area"
53
+ ghost-class="ghost"
54
+ >
55
+ <div v-for="element in group.children" :key="element.prop" class="drag-item">
56
+ <div class="item-content">
57
+ <i class="el-icon-rank drag-handle"></i>
58
+ <span class="item-label" :title="element.label">{{ element.configLabel || element.label }}</span>
59
+ </div>
60
+ <i class="el-icon-close close-btn" @click="removeColumn(element)"></i>
61
+ </div>
62
+ </draggable>
63
+ </div>
64
+ </div>
65
+ </div>
66
+ </div>
67
+
68
+ <span slot="footer" class="dialog-footer">
69
+ <el-button @click="visible = false">取 消</el-button>
70
+ <el-button type="primary" @click="confirm">确 定</el-button>
71
+ </span>
72
+ </el-dialog>
73
+ </template>
74
+
75
+ <script>
76
+ import draggable from 'vuedraggable'
77
+ import request from '@/utils/request'
78
+
79
+
80
+ export default {
81
+ name: 'DynamicDescriptionsConfig',
82
+ components: { draggable },
83
+ props: {
84
+ dialogVisible: Boolean,
85
+ title: { type: String, default: '配置显示列' },
86
+ columns: { type: Array, default: () => [] },
87
+ checkColumns: { type: Array, default: () => [] },
88
+ columnsGroups: { type: Array, default: () => [] },
89
+ pageId: { type: String, default: undefined },
90
+ autoSave: { type: Boolean, default: false },
91
+ labelKey: { type: String, default: 'label' },
92
+ isFilter: { type: Boolean, default: false },
93
+ isExport: { type: Boolean, default: false }
94
+ },
95
+ data() {
96
+ return {
97
+ localGroups: [],
98
+ userNo: sessionStorage.getItem('userNo')
99
+ }
100
+ },
101
+ computed: {
102
+ visible: {
103
+ get() { return this.dialogVisible },
104
+ set(val) { this.$emit('update:dialogVisible', val) }
105
+ }
106
+ },
107
+ watch: {
108
+ dialogVisible(val) {
109
+ if (val) {
110
+ this.init()
111
+ }
112
+ }
113
+ },
114
+ methods: {
115
+ init() {
116
+ // Initialize local structure
117
+ this.localGroups = this.columnsGroups.map(g => ({
118
+ ...g,
119
+ children: []
120
+ }))
121
+
122
+ // Populate with checkColumns based on their CURRENT groupId (or original if matching)
123
+ // Note: checkColumns may contain items with updated groupIds from previous drags
124
+ this.checkColumns.forEach(col => {
125
+ // Try to find the group this column currently claims to belong to
126
+ let group = this.localGroups.find(g => g.groupId === col.groupId)
127
+
128
+ // If not found (rare, or config changed), fallback to original define in 'columns'
129
+ if (!group) {
130
+ const originalDef = this.columns.find(c => c.prop === col.prop)
131
+ if (originalDef) {
132
+ group = this.localGroups.find(g => g.groupId === originalDef.groupId)
133
+ }
134
+ }
135
+
136
+ if (group) {
137
+ group.children.push({ ...col })
138
+ }
139
+ })
140
+ },
141
+ getGroupColumns(groupId) {
142
+ return this.columns.filter(c => c.groupId === groupId)
143
+ },
144
+ isChecked(col) {
145
+ for (const group of this.localGroups) {
146
+ if (group.children.some(child => child.prop === col.prop)) return true
147
+ }
148
+ return false
149
+ },
150
+ handleCheckChange(val, col) {
151
+ if (val) {
152
+ // Find original group to add to
153
+ const targetGroup = this.localGroups.find(g => g.groupId === col.groupId)
154
+ if (targetGroup) {
155
+ targetGroup.children.push({ ...col })
156
+ }
157
+ } else {
158
+ // Remove from wherever it is
159
+ for (const group of this.localGroups) {
160
+ const idx = group.children.findIndex(child => child.prop === col.prop)
161
+ if (idx !== -1) {
162
+ group.children.splice(idx, 1)
163
+ return
164
+ }
165
+ }
166
+ }
167
+ },
168
+ removeColumn(col) {
169
+ this.handleCheckChange(false, col)
170
+ },
171
+ getDragGroup(group) {
172
+ // Object groups share the 'object-groups' name allowing transfer
173
+ // Array groups are isolated with unique names
174
+ if (group.type === 'object') {
175
+ return 'object-groups'
176
+ }
177
+ return 'group-' + group.groupId
178
+ },
179
+ selectAll() {
180
+ this.localGroups.forEach(g => g.children = [])
181
+ this.columns.forEach(col => {
182
+ const group = this.localGroups.find(g => g.groupId === col.groupId)
183
+ if(group) group.children.push({...col})
184
+ })
185
+ },
186
+ clearAll() {
187
+ this.localGroups.forEach(g => g.children = [])
188
+ },
189
+ confirm() {
190
+ const result = []
191
+ this.localGroups.forEach(group => {
192
+ group.children.forEach(child => {
193
+ // Update groupId to reflect the current group
194
+ result.push({
195
+ ...child,
196
+ groupId: group.groupId
197
+ })
198
+ })
199
+ })
200
+
201
+ if (!this.isExport && this.autoSave) {
202
+ this.saveColumns(result)
203
+ }
204
+
205
+ this.$emit('setColumns', result)
206
+ this.visible = false
207
+ },
208
+ saveColumns(list) {
209
+ let columns = saveTransform(list, this.labelKey, this.isFilter)
210
+ request({
211
+ url: '/bems/prod_1.0/user/pageHabit',
212
+ method: 'post',
213
+ data: {
214
+ userNo: this.userNo,
215
+ pageId: this.pageId,
216
+ showStructure: JSON.stringify(columns)
217
+ },
218
+ loading: false
219
+ })
220
+ },
221
+ getColumns(pageId) {
222
+ return getColumns(pageId || this.pageId, this.userNo, this.columns, this.labelKey)
223
+ }
224
+ }
225
+ }
226
+
227
+ function saveTransform(list, labelKey, isFilter) {
228
+ let listN = []
229
+ if (!isFilter) {
230
+ list.forEach((c) => {
231
+ const extra = c.groupId ? { groupId: c.groupId } : {}
232
+ if (c.prop && !c.children && !c.isNew && !c.width) {
233
+ listN.push({ _colKey: 'prop', _colVal: c.prop, ...extra })
234
+ } else if (c.type && ['selection', 'index', 'expand'].includes(c.type)) {
235
+ listN.push({ _colKey: 'type', _colVal: c.type, ...extra })
236
+ } else {
237
+ let sFn = false
238
+ for (let k in c) {
239
+ if (typeof c[k] === 'function') sFn = true
240
+ }
241
+ if (sFn && c[labelKey]) {
242
+ listN.push({ _colKey: labelKey, _colVal: c[labelKey], ...extra })
243
+ } else {
244
+ listN.push(c)
245
+ }
246
+ }
247
+ })
248
+ } else {
249
+ list.forEach((c) => {
250
+ if (c.value) {
251
+ listN.push({ _colKey: 'value', _colVal: c.value })
252
+ } else if (c.startValue) {
253
+ listN.push({ _colKey: 'startValue', _colVal: c.startValue })
254
+ } else if (c.startDate) {
255
+ listN.push({ _colKey: 'startDate', _colVal: c.startDate })
256
+ } else {
257
+ listN.push(c)
258
+ }
259
+ })
260
+ }
261
+ return listN
262
+ }
263
+
264
+ function getTransform(list, columnsT, labelKey = 'label') {
265
+ let columns = []
266
+ list.forEach((d) => {
267
+ if (typeof d === 'string') {
268
+ let column = columnsT.find((c) => c[labelKey] === d)
269
+ column && columns.push(column)
270
+ } else if (typeof d === 'object') {
271
+ if (d._colKey) {
272
+ let column = columnsT.find((c) => c[d._colKey] === d._colVal)
273
+ if (column) {
274
+ let newCol = { ...column }
275
+ if (d.groupId) {
276
+ newCol.groupId = d.groupId
277
+ }
278
+ columns.push(newCol)
279
+ }
280
+ } else {
281
+ columns.push(d)
282
+ }
283
+ }
284
+ })
285
+ return columns
286
+ }
287
+
288
+ export function getColumns(pageId, userNo, columnsT, labelKey = 'label') {
289
+ return new Promise((resolve, reject) => {
290
+ request({
291
+ url: '/bems/prod_1.0/user/pageHabit/list',
292
+ method: 'post',
293
+ data: {
294
+ userNo: userNo,
295
+ pageId: pageId
296
+ },
297
+ loading: false
298
+ })
299
+ .then((res) => {
300
+ const data = res.data || res
301
+ if (data) {
302
+ let _data = data
303
+ if (typeof data === 'string') {
304
+ try {
305
+ _data = JSON.parse(data)
306
+ } catch(e) { console.error(e) }
307
+ }
308
+ if (_data && _data.length > 0) {
309
+ let columns = getTransform(_data, columnsT, labelKey)
310
+ return resolve(columns)
311
+ }
312
+ }
313
+ return resolve(null)
314
+ })
315
+ .catch((err) => {
316
+ reject(err)
317
+ })
318
+ })
319
+ }
320
+ </script>
321
+
322
+ <style scoped lang="scss">
323
+ .dialog-content {
324
+ display: flex;
325
+ height: 500px;
326
+ border: 1px solid #dcdfe6;
327
+ border-radius: 4px;
328
+ }
329
+
330
+ .left-panel, .right-panel {
331
+ flex: 1;
332
+ display: flex;
333
+ flex-direction: column;
334
+ overflow: hidden;
335
+ }
336
+
337
+ .left-panel {
338
+ border-right: 1px solid #dcdfe6;
339
+ }
340
+
341
+ .panel-header {
342
+ padding: 10px 15px;
343
+ background-color: #f5f7fa;
344
+ border-bottom: 1px solid #ebeef5;
345
+ font-weight: bold;
346
+ display: flex;
347
+ justify-content: space-between;
348
+ align-items: center;
349
+ }
350
+
351
+ .panel-body {
352
+ flex: 1;
353
+ overflow-y: auto;
354
+ padding: 10px;
355
+ }
356
+
357
+ .header-action {
358
+ font-size: 12px;
359
+ }
360
+
361
+ .group-section {
362
+ margin-bottom: 15px;
363
+ }
364
+
365
+ .group-title {
366
+ font-weight: bold;
367
+ margin-bottom: 8px;
368
+ color: #303133;
369
+ }
370
+
371
+ .group-items {
372
+ display: flex;
373
+ flex-direction: column;
374
+ .el-checkbox {
375
+ margin-left: 0 !important;
376
+ margin-bottom: 5px;
377
+ margin-right: 0;
378
+ }
379
+ }
380
+
381
+ .drag-group-section {
382
+ margin-bottom: 15px;
383
+ border: 1px dashed #e4e7ed;
384
+ border-radius: 4px;
385
+ padding: 5px;
386
+
387
+ &:hover {
388
+ border-color: #409EFF;
389
+ }
390
+ }
391
+
392
+ .group-title-bar {
393
+ display: flex;
394
+ justify-content: space-between;
395
+ padding: 5px 10px;
396
+ background: #fafafa;
397
+ margin-bottom: 5px;
398
+ border-radius: 2px;
399
+ }
400
+
401
+ .font-bold {
402
+ font-weight: 600;
403
+ color: #606266;
404
+ }
405
+
406
+ .count-badge {
407
+ color: #909399;
408
+ font-size: 12px;
409
+ }
410
+
411
+ .drag-area {
412
+ min-height: 40px;
413
+ }
414
+
415
+ .drag-item {
416
+ display: flex;
417
+ align-items: center;
418
+ justify-content: space-between;
419
+ padding: 6px 10px;
420
+ background-color: #fff;
421
+ border: 1px solid #ebeef5;
422
+ margin-bottom: 4px;
423
+ border-radius: 4px;
424
+ cursor: move;
425
+ transition: all 0.2s;
426
+
427
+ &:hover {
428
+ border-color: #c6e2ff;
429
+ background-color: #ecf5ff;
430
+
431
+ .close-btn {
432
+ opacity: 1;
433
+ }
434
+ }
435
+ }
436
+
437
+ .item-content {
438
+ display: flex;
439
+ align-items: center;
440
+ overflow: hidden;
441
+ }
442
+
443
+ .drag-handle {
444
+ color: #909399;
445
+ margin-right: 8px;
446
+ cursor: move;
447
+ }
448
+
449
+ .item-label {
450
+ font-size: 13px;
451
+ color: #606266;
452
+ white-space: nowrap;
453
+ overflow: hidden;
454
+ text-overflow: ellipsis;
455
+ }
456
+
457
+ .close-btn {
458
+ color: #f56c6c;
459
+ cursor: pointer;
460
+ opacity: 0;
461
+ transition: opacity 0.2s;
462
+ padding: 2px;
463
+
464
+ &:hover {
465
+ background-color: #fef0f0;
466
+ border-radius: 50%;
467
+ }
468
+ }
469
+
470
+ .ghost {
471
+ opacity: 0.5;
472
+ background: #c8ebfb;
473
+ }
474
+ </style>
@@ -0,0 +1,84 @@
1
+ import axios from 'axios'
2
+ import { Message } from 'element-ui'
3
+ function randomString(num) {
4
+ num = num || 32
5
+ var t = ''
6
+ t = 'ABCDEFGHJKMNPQRSTWXYZabcdefhijkmnprstwxyz2345678'
7
+
8
+ const a = t.length
9
+ let n = ''
10
+ for (let i = 0; i < num; i++) n += t.charAt(Math.floor(Math.random() * a))
11
+ return n
12
+ }
13
+ // 创建 axios 实例
14
+ const service = axios.create({
15
+ baseURL: '/',
16
+ timeout: 10000 // 设置请求超时时间
17
+ })
18
+
19
+ // 请求拦截器
20
+ service.interceptors.request.use(
21
+ config => {
22
+ // 可在此处添加认证头,例如 Token
23
+ const token = sessionStorage.getItem('token')
24
+ if (token) {
25
+ config.headers = {
26
+ Authorization: `Bearer ${token}`,
27
+ charset: 'utf-8',
28
+ lang: 'zh_CN',
29
+ timestamp: Date.now(),
30
+ requestKey: randomString(16),
31
+ OperationDesc: 'yYarJp'
32
+ }
33
+
34
+ }
35
+ return config
36
+ },
37
+ error => {
38
+ // 对请求错误做些什么
39
+ console.error('Request Error:', error) // for debug
40
+ return Promise.reject(error)
41
+ }
42
+ )
43
+
44
+ // 响应拦截器
45
+ service.interceptors.response.use(
46
+ /**
47
+ * 如果您想获取诸如 http 头或状态之类的 http 信息
48
+ * 请返回 response => response
49
+ */
50
+
51
+ /**
52
+ * 通过自定义代码确定请求状态
53
+ * 这里只是一个例子
54
+ * 您也可以通过 HTTP 状态码来判断状态
55
+ */
56
+ response => {
57
+ const res = response.data
58
+
59
+ // 后端自定义的成功码不为 200 或 0,则判定为错误。
60
+ if (res.code !== 200 && res.code !== 0) {
61
+ Message({
62
+ message: res.msg || 'Error',
63
+ type: 'error',
64
+ duration: 5 * 1000
65
+ })
66
+ return Promise.reject(new Error(res.msg || 'Error'))
67
+ } else {
68
+ // 成功则直接返回数据
69
+ return res
70
+ }
71
+ },
72
+ error => {
73
+ // 处理 HTTP 网络错误
74
+ console.error('Response Error:', error) // for debug
75
+ Message({
76
+ message: error.message,
77
+ type: 'error',
78
+ duration: 5 * 1000
79
+ })
80
+ return Promise.reject(error)
81
+ }
82
+ )
83
+
84
+ export default service