vue2-client 1.18.38 → 1.18.40

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.
@@ -1,125 +1,123 @@
1
- <template>
2
- <a-card :bordered="false">
3
- <x-form-table
4
- title="示例表单"
5
- :queryParamsName="queryParamsName"
6
- :fixedAddForm="fixedAddForm"
7
- :externalSelectedRowKeys="selectedKeys"
8
- @action="action"
9
- @selectRow="selectRow"
10
- @columnClick="columnClick"
11
- @ceshi="ceshi"
12
- @rowDblClick="rowDblClick"
13
- :defaultPageSize="5"
14
- :defaultQueryForm="{
15
- s_f_user_name: '张三'
16
- }"
17
- serviceName="af-safecheck"
18
- ref="xFormTable"
19
- >
20
- </x-form-table>
21
- <div style="height: 100px;width: 100%; background-color: #F7FAFC;">
22
- </div>
23
- <div style="height: 100px; width: 100%; background-color: #f0f2f5;">
24
- </div>
25
- </a-card>
26
- </template>
27
-
28
- <script>
29
- import XFormTable from '@vue2-client/base-client/components/common/XFormTable/XFormTable.vue'
30
- import { microDispatch } from '@vue2-client/utils/microAppUtils'
31
-
32
- export default {
33
- name: 'Demo',
34
- components: {
35
- XFormTable,
36
- },
37
- data () {
38
- return {
39
- // 查询配置文件名
40
- queryParamsName: 'checkPlanListCRUD',
41
- // 查询配置左侧tree
42
- xTreeConfigName: 'addressType',
43
- // 新增表单固定值
44
- fixedAddForm: {},
45
- // 是否显示详情抽屉
46
- detailVisible: false,
47
- // 当前记录
48
- record: {},
49
- // 选中的行keys
50
- selectedKeys: [],
51
- selected: {
52
- keys: [],
53
- rows: [],
54
- },
55
- }
56
- },
57
- methods: {
58
- rowDblClick (record) {
59
- console.log('rowDblClick', record)
60
- },
61
- // input框 行編輯參數傳遞
62
- ceshi (...args) {
63
- // attr, value, currentRecord, currentIndex, nextRecord, nextIndex
64
- const [attr, value, currentRecord, currentIndex, nextRecord, nextIndex] =
65
- args
66
- console.log(
67
- 'ceshi',
68
- attr,
69
- value,
70
- currentRecord,
71
- currentIndex,
72
- nextRecord,
73
- nextIndex
74
- )
75
- // 示例:当按下 Enter 键且有下一行时,跳转到下一行的同一列
76
- // 可以在这里执行业务逻辑(例如:保存数据)
77
- console.log('当前行:', currentIndex, '下一行:', nextIndex)
78
- if (!nextIndex) {
79
- this.$message.warning('没有下一行')
80
- return
81
- }
82
- // 跳转到下一行的同一列
83
- this.$refs.xFormTable.$refs.xTable.focusInput(nextIndex, attr.model)
84
- },
85
- test () {
86
- this.$refs.xFormTable.setTableData([])
87
- },
88
- defaultF () {
89
- this.$refs.xFormTable.setTableSize('default')
90
- },
91
- middleF () {
92
- this.$refs.xFormTable.setTableSize('middle')
93
- },
94
- smallF () {
95
- this.$refs.xFormTable.setTableSize('small')
96
- },
97
- columnClick (key, value, record) {
98
- microDispatch({
99
- type: 'v3route',
100
- path: '/bingliguanli/dianzibingliluru',
101
- props: { selected: arguments[0].his_f_admission_id },
102
- })
103
- },
104
- action (record, id, actionType) {
105
- this.detailVisible = true
106
- console.log('触发了详情操作', record, id, actionType)
107
- },
108
- onClose () {
109
- this.detailVisible = false
110
- // 关闭详情之后重新查询表单
111
- this.$refs.xFormTable.refreshTable(true)
112
- },
113
- selectRow (selectedRowKeys, selectedRows) {
114
- this.selected = {
115
- keys: selectedRowKeys,
116
- rows: selectedRows,
117
- }
118
- console.log('selectedDemo', this.selected)
119
- },
120
- },
121
- computed: {},
122
- }
123
- </script>
124
-
125
- <style scoped></style>
1
+ <template>
2
+ <a-card :bordered="false">
3
+ <x-form-table
4
+ title="示例表单"
5
+ :queryParamsName="queryParamsName"
6
+ :fixedAddForm="fixedAddForm"
7
+ :externalSelectedRowKeys="selectedKeys"
8
+ @action="action"
9
+ @selectRow="selectRow"
10
+ @columnClick="columnClick"
11
+ @ceshi="ceshi"
12
+ @rowDblClick="rowDblClick"
13
+ :defaultPageSize="5"
14
+ :defaultQueryForm="{
15
+ s_f_user_name: '张三'
16
+ }"
17
+ serviceName="af-safecheck"
18
+ ref="xFormTable"
19
+ >
20
+ </x-form-table>
21
+ <div style="height: 100px; width: 100%; background-color: #f0f2f5;">
22
+ </div>
23
+ </a-card>
24
+ </template>
25
+
26
+ <script>
27
+ import XFormTable from '@vue2-client/base-client/components/common/XFormTable/XFormTable.vue'
28
+ import { microDispatch } from '@vue2-client/utils/microAppUtils'
29
+ export default {
30
+ name: 'Demo',
31
+ components: {
32
+ XFormTable,
33
+ },
34
+ data () {
35
+ return {
36
+ // 查询配置文件名
37
+ queryParamsName: 'checkPlanListCRUD',
38
+ // 查询配置左侧tree
39
+ xTreeConfigName: 'addressType',
40
+ // 新增表单固定值
41
+ fixedAddForm: {},
42
+ // 是否显示详情抽屉
43
+ detailVisible: false,
44
+ // 当前记录
45
+ record: {},
46
+ // 选中的行keys
47
+ selectedKeys: [],
48
+ selected: {
49
+ keys: [],
50
+ rows: [],
51
+ },
52
+ }
53
+ },
54
+
55
+ methods: {
56
+ // ========== 原有方法 ==========
57
+ rowDblClick (record) {
58
+ console.log('rowDblClick', record)
59
+ },
60
+ // input框 行編輯參數傳遞
61
+ ceshi (...args) {
62
+ // attr, value, currentRecord, currentIndex, nextRecord, nextIndex
63
+ const [attr, value, currentRecord, currentIndex, nextRecord, nextIndex] =
64
+ args
65
+ console.log(
66
+ 'ceshi',
67
+ attr,
68
+ value,
69
+ currentRecord,
70
+ currentIndex,
71
+ nextRecord,
72
+ nextIndex
73
+ )
74
+ // 示例:当按下 Enter 键且有下一行时,跳转到下一行的同一列
75
+ // 可以在这里执行业务逻辑(例如:保存数据)
76
+ console.log('当前行:', currentIndex, '下一行:', nextIndex)
77
+ if (!nextIndex) {
78
+ this.$message.warning('没有下一行')
79
+ return
80
+ }
81
+ // 跳转到下一行的同一列
82
+ this.$refs.xFormTable.$refs.xTable.focusInput(nextIndex, attr.model)
83
+ },
84
+ test () {
85
+ this.$refs.xFormTable.setTableData([])
86
+ },
87
+ defaultF () {
88
+ this.$refs.xFormTable.setTableSize('default')
89
+ },
90
+ middleF () {
91
+ this.$refs.xFormTable.setTableSize('middle')
92
+ },
93
+ smallF () {
94
+ this.$refs.xFormTable.setTableSize('small')
95
+ },
96
+ columnClick (key, value, record) {
97
+ microDispatch({
98
+ type: 'v3route',
99
+ path: '/bingliguanli/dianzibingliluru',
100
+ props: { selected: arguments[0].his_f_admission_id },
101
+ })
102
+ },
103
+ action (record, id, actionType) {
104
+ this.detailVisible = true
105
+ console.log('触发了详情操作', record, id, actionType)
106
+ },
107
+ onClose () {
108
+ this.detailVisible = false
109
+ // 关闭详情之后重新查询表单
110
+ this.$refs.xFormTable.refreshTable(true)
111
+ },
112
+ selectRow (selectedRowKeys, selectedRows) {
113
+ this.selected = {
114
+ keys: selectedRowKeys,
115
+ rows: selectedRows,
116
+ }
117
+ console.log('selectedDemo', this.selected)
118
+ },
119
+ },
120
+ }
121
+ </script>
122
+
123
+ <style scoped></style>
@@ -143,7 +143,7 @@
143
143
  'x-import-excel-button',
144
144
  'x-chart',
145
145
  'h-chart',
146
- 'x-simple-table'
146
+ 'x-simple-table'
147
147
  ].includes(cell.slotType)">
148
148
  <component
149
149
  :is="getComponentName(cell.slotConfig, cell.serviceName, cell.slotType)"
@@ -107,7 +107,7 @@
107
107
  </span>
108
108
  <span v-else-if="item.slotType === 'action'" :key="'action-' + c_index">
109
109
  <template v-if="item.actionArr && item.actionArr.length > 0">
110
- <a-dropdown placement="bottomCenter" :getPopupContainer=" triggerNode => { return triggerNode.parentNode } ">
110
+ <a-dropdown placement="bottomCenter" :getPopupContainer="getPopupContainer">
111
111
  <a class="ant-dropdown-link" @click="e => e.preventDefault()">
112
112
  {{ item.scopedSlots?.customRender || item.slotValue }} <a-icon type="down"/>
113
113
  </a>
@@ -271,6 +271,7 @@
271
271
  <a-dropdown
272
272
  placement="bottomRight"
273
273
  overlayClassName="x-table-action-dropdown"
274
+ :getPopupContainer="getPopupContainer"
274
275
  :overlayStyle="{
275
276
  whiteSpace: 'nowrap',
276
277
  maxWidth: '250px',
@@ -523,6 +524,22 @@ export default {
523
524
  },
524
525
  inject: ['tableContext'],
525
526
  methods: {
527
+ getPopupContainer (triggerNode) {
528
+ // 限制最多向上查找5层
529
+ let parent = triggerNode.parentNode
530
+ let depth = 0
531
+ const maxDepth = 10
532
+
533
+ while (parent && parent !== document.body && depth < maxDepth) {
534
+ if (parent.tagName === 'TBODY') {
535
+ return parent
536
+ }
537
+ parent = parent.parentNode
538
+ depth++
539
+ }
540
+ // 如果5层内没找到,回退到body
541
+ return document.body
542
+ },
526
543
  handleRowClick (record) {
527
544
  this.$emit('rowClick', record)
528
545
  },
@@ -0,0 +1,175 @@
1
+ <template>
2
+ <a-card title="useRunLogic / usePost 请求工具示例" :bordered="false">
3
+ <a-space direction="vertical" style="width: 100%">
4
+ <!-- 示例1: 基础用法 - 组件级 loading -->
5
+ <a-button
6
+ type="primary"
7
+ :loading="basicRequest.loading"
8
+ @click="handleBasicRequest"
9
+ >
10
+ 基础请求 (组件级 loading)
11
+ </a-button>
12
+
13
+ <!-- 示例2: 全局 Loading -->
14
+ <a-button
15
+ type="danger"
16
+ :loading="globalLoadingRequest.loading"
17
+ @click="handleGlobalLoadingRequest"
18
+ >
19
+ 全局 Loading 请求
20
+ </a-button>
21
+
22
+ <!-- 示例3: 请求去重 -->
23
+ <a-button
24
+ type="dashed"
25
+ @click="handleDedupeRequest"
26
+ >
27
+ 去重请求 (快速点击试试)
28
+ </a-button>
29
+
30
+ <!-- 示例4: 组合使用 -->
31
+ <a-button
32
+ :loading="comboRequest.loading"
33
+ @click="handleComboRequest"
34
+ >
35
+ 组合使用 (去重 + 全局 Loading)
36
+ </a-button>
37
+
38
+ <!-- 示例5: usePost 基础用法 -->
39
+ <a-button
40
+ type="primary"
41
+ ghost
42
+ :loading="postRequest.loading"
43
+ @click="handlePostRequest"
44
+ >
45
+ usePost 示例
46
+ </a-button>
47
+
48
+ <!-- 显示请求结果 -->
49
+ <a-card v-if="requestResult" size="small" title="请求结果">
50
+ <pre style="margin: 0; font-size: 12px; max-height: 200px; overflow: auto;">{{ JSON.stringify(requestResult, null, 2) }}</pre>
51
+ </a-card>
52
+ </a-space>
53
+ </a-card>
54
+ </template>
55
+
56
+ <script>
57
+ import { useRunLogic, usePost } from '@vue2-client/composables'
58
+
59
+ export default {
60
+ name: 'UseRequestDemo',
61
+
62
+ data () {
63
+ return {
64
+ // 请求结果展示
65
+ requestResult: null,
66
+
67
+ // ========== useRunLogic 实例 ==========
68
+ // 示例1: 基础用法
69
+ basicRequest: useRunLogic('test'),
70
+
71
+ // 示例2: 全局 Loading
72
+ globalLoadingRequest: useRunLogic('test', {
73
+ globalLoading: '正在加载数据...'
74
+ }),
75
+
76
+ // 示例3: 请求去重
77
+ dedupeRequest: useRunLogic('test', {
78
+ dedupe: true
79
+ }),
80
+
81
+ // 示例4: 组合使用
82
+ comboRequest: useRunLogic('test', {
83
+ dedupe: true,
84
+ globalLoading: '处理中,请勿重复操作...'
85
+ }),
86
+
87
+ // ========== usePost 实例 ==========
88
+ postRequest: usePost('/api/af-safecheck/logic/test', {
89
+ globalLoading: '请求中...'
90
+ })
91
+ }
92
+ },
93
+
94
+ methods: {
95
+ // 示例1: 基础请求
96
+ async handleBasicRequest () {
97
+ const { success, data, error } = await this.basicRequest.execute({
98
+ queryParamsName: 'checkPlanListCRUD',
99
+ pageNum: 1,
100
+ pageSize: 5
101
+ })
102
+ if (success) {
103
+ this.requestResult = data
104
+ this.$message.success('基础请求成功')
105
+ } else if (error?.code !== 'ERR_DUPLICATE_REQUEST') {
106
+ this.$message.error('请求失败: ' + error?.message)
107
+ }
108
+ },
109
+
110
+ // 示例2: 全局 Loading 请求
111
+ async handleGlobalLoadingRequest () {
112
+ const { success, data, error } = await this.globalLoadingRequest.execute({
113
+ queryParamsName: 'checkPlanListCRUD',
114
+ pageNum: 1,
115
+ pageSize: 5
116
+ })
117
+ if (success) {
118
+ this.requestResult = data
119
+ this.$message.success('全局 Loading 请求成功')
120
+ } else if (error?.code !== 'ERR_DUPLICATE_REQUEST') {
121
+ this.$message.error('请求失败: ' + error?.message)
122
+ }
123
+ },
124
+
125
+ // 示例3: 去重请求
126
+ async handleDedupeRequest () {
127
+ // 快速点击多次,只会发送一次请求
128
+ const { success, data, error } = await this.dedupeRequest.execute({
129
+ queryParamsName: 'checkPlanListCRUD',
130
+ pageNum: 1,
131
+ pageSize: 5
132
+ })
133
+ if (success) {
134
+ this.requestResult = data
135
+ this.$message.success('去重请求成功 (查看控制台是否有去重警告)')
136
+ } else if (error?.code === 'ERR_DUPLICATE_REQUEST') {
137
+ // 重复请求被拦截,静默处理或提示
138
+ console.log('重复请求已被拦截')
139
+ } else {
140
+ this.$message.error('请求失败: ' + error?.message)
141
+ }
142
+ },
143
+
144
+ // 示例4: 组合请求
145
+ async handleComboRequest () {
146
+ const { success, data, error } = await this.comboRequest.execute({
147
+ queryParamsName: 'checkPlanListCRUD',
148
+ pageNum: 1,
149
+ pageSize: 5
150
+ })
151
+ if (success) {
152
+ this.requestResult = data
153
+ this.$message.success('组合请求成功')
154
+ } else if (error?.code !== 'ERR_DUPLICATE_REQUEST') {
155
+ this.$message.error('请求失败: ' + error?.message)
156
+ }
157
+ },
158
+
159
+ // 示例5: usePost 请求
160
+ async handlePostRequest () {
161
+ const { success, data, error } = await this.postRequest.execute({
162
+ queryParamsName: 'checkPlanListCRUD',
163
+ pageNum: 1,
164
+ pageSize: 5
165
+ })
166
+ if (success) {
167
+ this.requestResult = data
168
+ this.$message.success('usePost 请求成功')
169
+ } else if (error?.code !== 'ERR_DUPLICATE_REQUEST') {
170
+ this.$message.error('请求失败: ' + error?.message)
171
+ }
172
+ }
173
+ }
174
+ }
175
+ </script>
@@ -0,0 +1,6 @@
1
+ /**
2
+ * Vue Composables 入口文件
3
+ * 提供 Composition API 风格的可复用逻辑
4
+ */
5
+
6
+ export { usePost, createPost, useRunLogic, createRunLogic } from './usePost'
@@ -0,0 +1,206 @@
1
+ /**
2
+ * 全局 Loading 管理
3
+ * 纯 JS 实现,直接操作 DOM,不依赖 Vue 组件
4
+ * 支持引用计数,多个并发请求时正确处理显示/隐藏
5
+ * 支持超时自动隐藏保护机制
6
+ */
7
+
8
+ // 全局状态
9
+ let isLoading = false
10
+ let loadingElement = null
11
+ let messageText = '加载中...'
12
+ let loadingCount = 0 // 引用计数,支持并发请求
13
+ let autoHideTimer = null // 超时自动隐藏定时器
14
+
15
+ // 配置
16
+ const DEFAULT_TIMEOUT = 30000 // 默认超时时间 30 秒
17
+ const CONTAINER_ID = 'global-loading-container'
18
+
19
+ /**
20
+ * 创建 Loading DOM 元素
21
+ */
22
+ function createLoadingElement (message) {
23
+ const container = document.createElement('div')
24
+ container.id = CONTAINER_ID
25
+ container.innerHTML = `
26
+ <div class="global-loading-mask">
27
+ <div class="global-loading-content">
28
+ <div class="global-loading-spinner">
29
+ <div class="ant-spin ant-spin-lg ant-spin-spinning">
30
+ <span class="ant-spin-dot ant-spin-dot-spin">
31
+ <i class="ant-spin-dot-item"></i>
32
+ <i class="ant-spin-dot-item"></i>
33
+ <i class="ant-spin-dot-item"></i>
34
+ <i class="ant-spin-dot-item"></i>
35
+ </span>
36
+ </div>
37
+ </div>
38
+ <p class="global-loading-message">${message}</p>
39
+ </div>
40
+ </div>
41
+ `
42
+ return container
43
+ }
44
+
45
+ /**
46
+ * 注入全局样式(只注入一次)
47
+ */
48
+ function injectStyles () {
49
+ if (document.getElementById('global-loading-styles')) return
50
+
51
+ const style = document.createElement('style')
52
+ style.id = 'global-loading-styles'
53
+ style.textContent = `
54
+ .global-loading-mask {
55
+ position: fixed;
56
+ top: 0;
57
+ left: 0;
58
+ right: 0;
59
+ bottom: 0;
60
+ background: rgba(0, 0, 0, 0.45);
61
+ z-index: 99999;
62
+ display: flex;
63
+ align-items: center;
64
+ justify-content: center;
65
+ }
66
+ .global-loading-content {
67
+ text-align: center;
68
+ }
69
+ .global-loading-message {
70
+ margin-top: 16px;
71
+ font-size: 14px;
72
+ color: #fff;
73
+ }
74
+ .global-loading-content .ant-spin-dot-item {
75
+ background-color: #fff !important;
76
+ }
77
+ `
78
+ document.head.appendChild(style)
79
+ }
80
+
81
+ /**
82
+ * 清除超时定时器
83
+ */
84
+ function clearAutoHideTimer () {
85
+ if (autoHideTimer) {
86
+ clearTimeout(autoHideTimer)
87
+ autoHideTimer = null
88
+ }
89
+ }
90
+
91
+ /**
92
+ * 设置超时自动隐藏
93
+ * @param {number} timeout - 超时时间(毫秒)
94
+ */
95
+ function setAutoHideTimer (timeout) {
96
+ clearAutoHideTimer()
97
+ if (timeout > 0) {
98
+ autoHideTimer = setTimeout(() => {
99
+ console.warn(`[GlobalLoading] 超时自动隐藏 (${timeout}ms)`)
100
+ forceHideGlobalLoading()
101
+ }, timeout)
102
+ }
103
+ }
104
+
105
+ /**
106
+ * 显示全局 Loading
107
+ * 支持引用计数,多次调用只显示一个 Loading
108
+ * @param {string|boolean} message - 提示信息,传 true 使用默认文字
109
+ * @param {number} timeout - 超时自动隐藏时间(毫秒),默认 30 秒,传 0 禁用
110
+ */
111
+ export function showGlobalLoading (message = '加载中...', timeout = DEFAULT_TIMEOUT) {
112
+ loadingCount++
113
+
114
+ // 已经在显示,只增加计数,重置超时
115
+ if (isLoading) {
116
+ setAutoHideTimer(timeout)
117
+ return
118
+ }
119
+
120
+ isLoading = true
121
+ messageText = typeof message === 'string' ? message : '加载中...'
122
+
123
+ // 注入样式
124
+ injectStyles()
125
+
126
+ // 创建并插入 DOM
127
+ loadingElement = createLoadingElement(messageText)
128
+ document.body.appendChild(loadingElement)
129
+
130
+ // 设置超时自动隐藏
131
+ setAutoHideTimer(timeout)
132
+ }
133
+
134
+ /**
135
+ * 隐藏全局 Loading
136
+ * 只有当所有请求都完成时才真正隐藏
137
+ */
138
+ export function hideGlobalLoading () {
139
+ loadingCount = Math.max(0, loadingCount - 1)
140
+
141
+ // 还有其他请求在进行中,不隐藏
142
+ if (loadingCount > 0) return
143
+
144
+ doHide()
145
+ }
146
+
147
+ /**
148
+ * 强制隐藏全局 Loading(重置计数器)
149
+ * 用于异常情况下的强制重置
150
+ */
151
+ export function forceHideGlobalLoading () {
152
+ loadingCount = 0
153
+ doHide()
154
+ }
155
+
156
+ /**
157
+ * 执行隐藏操作
158
+ */
159
+ function doHide () {
160
+ if (!isLoading) return
161
+
162
+ isLoading = false
163
+ clearAutoHideTimer()
164
+
165
+ // 移除 DOM
166
+ if (loadingElement && loadingElement.parentNode) {
167
+ loadingElement.parentNode.removeChild(loadingElement)
168
+ }
169
+ loadingElement = null
170
+ }
171
+
172
+ /**
173
+ * 获取当前 Loading 状态
174
+ * @returns {boolean}
175
+ */
176
+ export function isGlobalLoadingVisible () {
177
+ return isLoading
178
+ }
179
+
180
+ /**
181
+ * 获取当前 Loading 计数
182
+ * @returns {number}
183
+ */
184
+ export function getLoadingCount () {
185
+ return loadingCount
186
+ }
187
+
188
+ /**
189
+ * useGlobalLoading Hook(Vue Composition API 风格)
190
+ * @returns {{ show: Function, hide: Function, forceHide: Function, isVisible: Function }}
191
+ */
192
+ export function useGlobalLoading () {
193
+ return {
194
+ show: showGlobalLoading,
195
+ hide: hideGlobalLoading,
196
+ forceHide: forceHideGlobalLoading,
197
+ isVisible: isGlobalLoadingVisible
198
+ }
199
+ }
200
+
201
+ export default {
202
+ show: showGlobalLoading,
203
+ hide: hideGlobalLoading,
204
+ forceHide: forceHideGlobalLoading,
205
+ isVisible: isGlobalLoadingVisible
206
+ }