vue2-client 1.13.3 → 1.13.5

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,107 +1,107 @@
1
- {
2
- "name": "vue2-client",
3
- "version": "1.13.3",
4
- "private": false,
5
- "scripts": {
6
- "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint",
7
- "serve:gaslink": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode gaslink",
8
- "serve:revenue": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode revenue",
9
- "serve:liuli": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode liuli",
10
- "serve:scada": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode scada",
11
- "serve:iot": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode iot",
12
- "serve:his": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode his",
13
- "mac-serve": "vue-cli-service serve --no-eslint --mode his",
14
- "build": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
15
- "test:unit": "vue-cli-service test:unit",
16
- "lint": "vue-cli-service lint",
17
- "build:preview": "vue-cli-service build --mode preview",
18
- "lint:nofix": "vue-cli-service lint --no-fix",
19
- "test": "jest"
20
- },
21
- "dependencies": {
22
- "@afwenming123/vue-easy-tree": "^1.0.1",
23
- "@afwenming123/vue-plugin-hiprint": "^0.0.70",
24
- "@amap/amap-jsapi-loader": "^1.0.1",
25
- "@antv/data-set": "^0.11.8",
26
- "@antv/g2plot": "^2.4.31",
27
- "@hufe921/canvas-editor": "^0.9.49",
28
- "@microsoft/fetch-event-source": "^2.0.1",
29
- "@vue/babel-preset-jsx": "^1.4.0",
30
- "animate.css": "^4.1.1",
31
- "ant-design-vue": "^1.7.8",
32
- "axios": "^0.27.2",
33
- "clipboard": "^2.0.11",
34
- "core-js": "^3.33.0",
35
- "crypto-js": "^4.1.1",
36
- "date-fns": "^2.29.3",
37
- "default-passive-events": "^2.0.0",
38
- "dotenv": "^16.3.1",
39
- "echarts": "^5.5.0",
40
- "enquire.js": "^2.1.6",
41
- "file-saver": "^2.0.5",
42
- "highlight.js": "^11.7.0",
43
- "html2canvas": "^1.4.1",
44
- "js-base64": "^3.7.5",
45
- "js-cookie": "^2.2.1",
46
- "jsencrypt": "^3.3.2",
47
- "jspdf": "^2.5.1",
48
- "lodash.clonedeep": "^4.5.0",
49
- "lodash.debounce": "^4",
50
- "lodash.get": "^4.4.2",
51
- "marked": "^4",
52
- "mockjs": "^1.1.0",
53
- "nprogress": "^0.2.0",
54
- "qs": "^6.11.2",
55
- "regenerator-runtime": "^0.14.0",
56
- "videojs-contrib-hls": "^5.15.0",
57
- "viser-vue": "^2.4.8",
58
- "vue": "^2.7.14",
59
- "vue-codemirror": "4.0.6",
60
- "vue-draggable-resizable": "^2",
61
- "vue-i18n": "^8.28.2",
62
- "vue-json-viewer": "^2.2.22",
63
- "vue-router": "^3.6.5",
64
- "vue-video-player": "^5.0.2",
65
- "vue-virtual-scroller": "^1.1.2",
66
- "vuedraggable": "^2.24.3",
67
- "vuex": "^3.6.2",
68
- "xlsx": "0.18.5"
69
- },
70
- "devDependencies": {
71
- "@ant-design/colors": "^7.0.0",
72
- "@babel/core": "^7.22.20",
73
- "@babel/eslint-parser": "^7.22.15",
74
- "@babel/preset-env": "^7.22.20",
75
- "@vue/cli-plugin-babel": "^5.0.8",
76
- "@vue/cli-plugin-eslint": "^5.0.8",
77
- "@vue/cli-service": "^5.0.8",
78
- "@vue/eslint-config-standard": "^8.0.1",
79
- "@vue/test-utils": "^1.3.6",
80
- "babel-plugin-transform-remove-console": "^6.9.4",
81
- "compression-webpack-plugin": "^10.0.0",
82
- "css-minimizer-webpack-plugin": "^5.0.1",
83
- "deepmerge": "^4.3.1",
84
- "eslint": "^8.51.0",
85
- "eslint-plugin-vue": "^9.17.0",
86
- "fast-deep-equal": "^3.1.3",
87
- "ignore-loader": "^0.1.2",
88
- "jest": "^29.7.0",
89
- "jest-environment-jsdom": "^29.7.0",
90
- "jest-transform-stub": "^2.0.0",
91
- "less-loader": "^6.2.0",
92
- "script-loader": "^0.7.2",
93
- "style-resources-loader": "^1.5.0",
94
- "vue-cli-plugin-style-resources-loader": "^0.1.5",
95
- "vue-jest": "^4.0.1",
96
- "vue-template-compiler": "^2.7.14",
97
- "webpack": "^5.88.2",
98
- "webpack-theme-color-replacer": "^1.4.7",
99
- "whatwg-fetch": "^3.6.19"
100
- },
101
- "browserslist": [
102
- "> 1%",
103
- "last 2 versions",
104
- "not dead",
105
- "not ie 11"
106
- ]
107
- }
1
+ {
2
+ "name": "vue2-client",
3
+ "version": "1.13.5",
4
+ "private": false,
5
+ "scripts": {
6
+ "serve": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint",
7
+ "serve:gaslink": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode gaslink",
8
+ "serve:revenue": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode revenue",
9
+ "serve:liuli": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode liuli",
10
+ "serve:scada": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode scada",
11
+ "serve:iot": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode iot",
12
+ "serve:his": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service serve --no-eslint --mode his",
13
+ "mac-serve": "vue-cli-service serve --no-eslint --mode his",
14
+ "build": "SET NODE_OPTIONS=--openssl-legacy-provider && vue-cli-service build",
15
+ "test:unit": "vue-cli-service test:unit",
16
+ "lint": "vue-cli-service lint",
17
+ "build:preview": "vue-cli-service build --mode preview",
18
+ "lint:nofix": "vue-cli-service lint --no-fix",
19
+ "test": "jest"
20
+ },
21
+ "dependencies": {
22
+ "@afwenming123/vue-easy-tree": "^1.0.1",
23
+ "@afwenming123/vue-plugin-hiprint": "^0.0.70",
24
+ "@amap/amap-jsapi-loader": "^1.0.1",
25
+ "@antv/data-set": "^0.11.8",
26
+ "@antv/g2plot": "^2.4.31",
27
+ "@hufe921/canvas-editor": "^0.9.49",
28
+ "@microsoft/fetch-event-source": "^2.0.1",
29
+ "@vue/babel-preset-jsx": "^1.4.0",
30
+ "animate.css": "^4.1.1",
31
+ "ant-design-vue": "^1.7.8",
32
+ "axios": "^0.27.2",
33
+ "clipboard": "^2.0.11",
34
+ "core-js": "^3.33.0",
35
+ "crypto-js": "^4.1.1",
36
+ "date-fns": "^2.29.3",
37
+ "default-passive-events": "^2.0.0",
38
+ "dotenv": "^16.3.1",
39
+ "echarts": "^5.5.0",
40
+ "enquire.js": "^2.1.6",
41
+ "file-saver": "^2.0.5",
42
+ "highlight.js": "^11.7.0",
43
+ "html2canvas": "^1.4.1",
44
+ "js-base64": "^3.7.5",
45
+ "js-cookie": "^2.2.1",
46
+ "jsencrypt": "^3.3.2",
47
+ "jspdf": "^2.5.1",
48
+ "lodash.clonedeep": "^4.5.0",
49
+ "lodash.debounce": "^4",
50
+ "lodash.get": "^4.4.2",
51
+ "marked": "^4",
52
+ "mockjs": "^1.1.0",
53
+ "nprogress": "^0.2.0",
54
+ "qs": "^6.11.2",
55
+ "regenerator-runtime": "^0.14.0",
56
+ "videojs-contrib-hls": "^5.15.0",
57
+ "viser-vue": "^2.4.8",
58
+ "vue": "^2.7.14",
59
+ "vue-codemirror": "4.0.6",
60
+ "vue-draggable-resizable": "^2",
61
+ "vue-i18n": "^8.28.2",
62
+ "vue-json-viewer": "^2.2.22",
63
+ "vue-router": "^3.6.5",
64
+ "vue-video-player": "^5.0.2",
65
+ "vue-virtual-scroller": "^1.1.2",
66
+ "vuedraggable": "^2.24.3",
67
+ "vuex": "^3.6.2",
68
+ "xlsx": "0.18.5"
69
+ },
70
+ "devDependencies": {
71
+ "@ant-design/colors": "^7.0.0",
72
+ "@babel/core": "^7.22.20",
73
+ "@babel/eslint-parser": "^7.22.15",
74
+ "@babel/preset-env": "^7.22.20",
75
+ "@vue/cli-plugin-babel": "^5.0.8",
76
+ "@vue/cli-plugin-eslint": "^5.0.8",
77
+ "@vue/cli-service": "^5.0.8",
78
+ "@vue/eslint-config-standard": "^8.0.1",
79
+ "@vue/test-utils": "^1.3.6",
80
+ "babel-plugin-transform-remove-console": "^6.9.4",
81
+ "compression-webpack-plugin": "^10.0.0",
82
+ "css-minimizer-webpack-plugin": "^5.0.1",
83
+ "deepmerge": "^4.3.1",
84
+ "eslint": "^8.51.0",
85
+ "eslint-plugin-vue": "^9.17.0",
86
+ "fast-deep-equal": "^3.1.3",
87
+ "ignore-loader": "^0.1.2",
88
+ "jest": "^29.7.0",
89
+ "jest-environment-jsdom": "^29.7.0",
90
+ "jest-transform-stub": "^2.0.0",
91
+ "less-loader": "^6.2.0",
92
+ "script-loader": "^0.7.2",
93
+ "style-resources-loader": "^1.5.0",
94
+ "vue-cli-plugin-style-resources-loader": "^0.1.5",
95
+ "vue-jest": "^4.0.1",
96
+ "vue-template-compiler": "^2.7.14",
97
+ "webpack": "^5.88.2",
98
+ "webpack-theme-color-replacer": "^1.4.7",
99
+ "whatwg-fetch": "^3.6.19"
100
+ },
101
+ "browserslist": [
102
+ "> 1%",
103
+ "last 2 versions",
104
+ "not dead",
105
+ "not ie 11"
106
+ ]
107
+ }
@@ -1,6 +1,5 @@
1
1
  <template>
2
- <div class="x-input-wrapper" v-click-outside="handleClickOutside">
3
- <!-- 输入框部分 -->
2
+ <div class="x-input-wrapper">
4
3
  <a-input
5
4
  v-model="innerValue"
6
5
  v-bind="$attrs"
@@ -9,70 +8,25 @@
9
8
  :maxLength="config?.maxLength"
10
9
  :disabled="config?.disabled"
11
10
  :allowClear="config?.allowClear"
12
- @change="handleChange"
13
- @pressEnter="handlePressEnter"
14
- @focus="handleFocus"
11
+ @change="handleInput"
12
+ @pressEnter="handleSearch"
15
13
  >
16
14
  <template v-if="config?.prefix" #prefix>
17
- <a-icon :type="config.prefix" @click="handlePrefixClick" class="clickable-icon" />
15
+ <a-icon :type="config.prefix" @click="handleSearch" class="clickable-icon" />
18
16
  </template>
19
17
  <template v-if="config?.suffix" #suffix>
20
- <a-icon :type="config.suffix" @click="handleSuffixClick" class="clickable-icon" />
18
+ <a-icon :type="config.suffix" @click="handleSearch" class="clickable-icon" />
21
19
  </template>
22
20
  </a-input>
23
-
24
- <!-- 搜索结果列表 -->
25
- <div v-if="isListVisible" class="search-list">
26
- <a-list
27
- v-if="searchList.length > 0"
28
- size="small"
29
- bordered
30
- >
31
- <a-list-item
32
- v-for="item in searchList"
33
- :key="item.id"
34
- @click="handleSelect(item)"
35
- >
36
- <div class="item-content">
37
- <span class="item-name">{{ item.name }}</span>
38
- <span class="item-code">{{ item.code }}</span>
39
- </div>
40
- </a-list-item>
41
- </a-list>
42
- <div v-else class="empty-data">
43
- <a-empty :image="simpleImage" :description="'暂无数据'" />
44
- </div>
45
- </div>
46
21
  </div>
47
22
  </template>
48
23
 
49
24
  <script>
50
25
  import { getConfigByName, runLogic } from '@vue2-client/services/api/common'
51
- import debounce from 'lodash/debounce'
52
- import { Empty } from 'ant-design-vue'
53
-
54
- // 自定义指令:点击外部关闭
55
- const clickOutside = {
56
- bind (el, binding) {
57
- el.__vueClickOutside__ = function (event) {
58
- if (!(el === event.target || el.contains(event.target))) {
59
- binding.value(event)
60
- }
61
- }
62
- document.body.addEventListener('click', el.__vueClickOutside__)
63
- },
64
- unbind (el) {
65
- document.body.removeEventListener('click', el.__vueClickOutside__)
66
- delete el.__vueClickOutside__
67
- }
68
- }
69
26
 
70
27
  export default {
71
28
  name: 'XInput',
72
29
  inheritAttrs: false,
73
- directives: {
74
- 'click-outside': clickOutside
75
- },
76
30
  props: {
77
31
  value: {
78
32
  type: [String, Number],
@@ -80,117 +34,37 @@ export default {
80
34
  },
81
35
  queryParamsName: {
82
36
  type: String,
83
- default: 'inputTestConfig'
37
+ default: ''
84
38
  }
85
39
  },
86
40
  data () {
87
41
  return {
88
- innerValue: this.value,
89
- config: null,
90
- searchList: [],
91
- isListVisible: false,
92
- simpleImage: Empty.PRESENTED_IMAGE_SIMPLE
42
+ innerValue: this.value || '',
43
+ config: null
93
44
  }
94
45
  },
95
46
  created () {
96
47
  this.getData(this.queryParamsName)
97
- this.debouncedSearch = debounce(this.handleSearch, 300)
98
48
  },
49
+ emits: ['search'],
99
50
  methods: {
51
+ runLogic,
100
52
  async getData (data) {
101
53
  getConfigByName(data, 'af-his', res => {
102
- console.warn(res)
103
54
  this.config = res
104
55
  if (res.defaultValue !== undefined) {
105
56
  this.innerValue = res.defaultValue
106
- // 当配置中有默认值时,向父组件同步默认值
107
- this.$emit('input', res.defaultValue)
108
- }
109
- if (res.logicName) {
110
- runLogic(res.logicName, res.parameter, 'af-his').then(result => {
111
- if (result) {
112
- this.handleLogicResult(result)
113
- }
114
- })
115
57
  }
116
58
  })
117
59
  },
118
- handleChange (e) {
60
+ handleInput (e) {
119
61
  const value = e.target.value
120
- // 双向绑定:将输入框的值同步给父组件
121
- this.$emit('input', value)
122
- // 发送change事件:当输入框值变化时触发,携带原生事件对象
123
- this.$emit('change', e)
124
- this.debouncedSearch(value)
125
- },
126
- handlePressEnter (e) {
127
- // 发送回车事件:当用户按下回车键时触发
128
- this.$emit('pressEnter', e)
129
- this.handleSearch(e.target.value)
130
- },
131
- handleFocus () {
132
- this.isListVisible = true
133
- // 当获取焦点时,如果输入框为空,显示所有数据
134
- if (!this.innerValue) {
135
- this.handleSearch('')
136
- }
137
- },
138
- handleClickOutside () {
139
- this.isListVisible = false
140
- },
141
- async handleSearch (value) {
142
- if (!this.config?.searchConfig?.logicName) return
143
-
144
- try {
145
- // 调用后端搜索接口
146
- const result = await runLogic(
147
- this.config.searchConfig.logicName,
148
- {
149
- ...this.config.searchConfig.parameter,
150
- searchValue: value
151
- },
152
- 'af-his'
153
- )
154
-
155
- if (result) {
156
- // 更新搜索结果列表
157
- this.searchList = result || []
158
- console.warn(result)
159
- // 发送搜索结果
160
- this.$emit('search-result', result)
161
- }
162
- } catch (error) {
163
- console.error('搜索失败:', error)
164
- this.searchList = []
165
- }
62
+ this.innerValue = value
63
+ this.$emit('search', value)
166
64
  },
167
- handlePrefixClick (e) {
168
- e.stopPropagation()
169
- // 发送前缀图标点击事件:当用户点击输入框前缀图标时触发
170
- this.$emit('prefix-click', this.innerValue)
171
- this.handleSearch(this.innerValue)
172
- },
173
- handleSuffixClick (e) {
174
- e.stopPropagation()
175
- // 发送后缀图标点击事件:当用户点击输入框后缀图标时触发
176
- this.$emit('suffix-click', this.innerValue)
177
- this.handleSearch(this.innerValue)
178
- },
179
- handleLogicResult (result) {
180
- if (result.value !== undefined) {
181
- this.innerValue = result.value
182
- // 发送输入值:当逻辑处理返回新值时,同步给父组件
183
- this.$emit('input', result.value)
184
- }
185
- },
186
- handleSelect (item) {
187
- this.innerValue = item.name
188
- this.searchList = [] // 清空列表
189
- this.isListVisible = false // 选择后关闭列表
190
- // 发送选中项的名称:用于更新输入框的值
191
- this.$emit('input', item.name)
192
- // 发送选中事件:当用户从搜索结果列表中选择一项时触发,携带完整的选中项数据
193
- this.$emit('select', item)
65
+ handleSearch () {
66
+ // 统一的搜索事件:将当前输入值发送给父组件
67
+ this.$emit('search', this.innerValue)
194
68
  }
195
69
  },
196
70
  watch: {
@@ -206,9 +80,6 @@ export default {
206
80
  },
207
81
  deep: true
208
82
  }
209
- },
210
- beforeDestroy () {
211
- this.debouncedSearch.cancel()
212
83
  }
213
84
  }
214
85
  </script>
@@ -220,36 +91,6 @@ export default {
220
91
  width: 100%;
221
92
  }
222
93
 
223
- .search-list {
224
- position: absolute;
225
- top: 100%;
226
- left: 0;
227
- width: 100%;
228
- max-height: 300px;
229
- overflow-y: auto;
230
- background: #fff;
231
- z-index: 1000;
232
- margin-top: 4px;
233
- box-shadow: 0 2px 8px rgba(0, 0, 0, 0.15);
234
- }
235
-
236
- .item-content {
237
- display: flex;
238
- justify-content: space-between;
239
- width: 100%;
240
- padding: 8px 12px;
241
- cursor: pointer;
242
- }
243
-
244
- .item-content:hover {
245
- background-color: #f5f5f5;
246
- }
247
-
248
- .item-code {
249
- color: rgba(0, 0, 0, 0.45);
250
- font-size: 12px;
251
- }
252
-
253
94
  :deep(.clickable-icon) {
254
95
  cursor: pointer;
255
96
  }
@@ -257,11 +98,4 @@ export default {
257
98
  :deep(.clickable-icon:hover) {
258
99
  color: #1890ff;
259
100
  }
260
- .empty-data {
261
- padding: 16px;
262
- text-align: center;
263
- background: #fff;
264
- border: 1px solid #d9d9d9;
265
- border-radius: 2px;
266
- }
267
101
  </style>
@@ -0,0 +1,95 @@
1
+ # XInput 组件
2
+
3
+ XInput 是一个封装了 Ant Design Vue 的输入框组件,提供搜索功能和自定义配置。
4
+
5
+ ## 功能特点
6
+
7
+ - 支持从配置中获取参数
8
+ - 支持前缀和后缀图标
9
+ - 集成搜索功能
10
+ - 支持双向绑定
11
+
12
+ ## 配置选项
13
+
14
+ 组件通过 `queryParamsName` 属性获取配置,配置内容如下:
15
+
16
+ | 参数 | 说明 | 类型 | 默认值 |
17
+ | --- | --- | --- | --- |
18
+ | size | 输入框大小 | string | 'default' |
19
+ | prefix | 前缀图标类型 | string | '' |
20
+ | defaultValue | 默认值 | string/number | '' |
21
+ | disabled | 是否禁用 | boolean | false |
22
+ | placeholder | 占位文本 | string | '' |
23
+ | allowClear | 是否允许清除 | boolean | true |
24
+ | suffix | 后缀图标类型 | string | 'search' |
25
+ | maxLength | 最大输入长度 | number | 50 |
26
+
27
+ ## 事件
28
+
29
+ | 事件名称 | 说明 | 回调参数 |
30
+ | --- | --- | --- |
31
+ | search | 搜索事件,在输入值变化、按下回车键或点击图标时触发 | function(value) |
32
+
33
+ ## 使用示例
34
+
35
+ ### 基础用法
36
+
37
+ ```vue
38
+ <template>
39
+ <x-input
40
+ v-model="searchValue"
41
+ @search="handleSearch"
42
+ />
43
+ </template>
44
+
45
+ <script>
46
+ export default {
47
+ data() {
48
+ return {
49
+ searchValue: ''
50
+ }
51
+ },
52
+ methods: {
53
+ handleSearch(value) {
54
+ console.log('搜索值:', value)
55
+ // 执行搜索逻辑
56
+ }
57
+ }
58
+ }
59
+ </script>
60
+ ```
61
+
62
+ ### 自定义配置
63
+
64
+ 在后端配置中定义如下配置:
65
+
66
+ ```json
67
+ {
68
+ "size": "default",
69
+ "prefix": "",
70
+ "defaultValue": "",
71
+ "disabled": false,
72
+ "placeholder": "请输入关键词",
73
+ "allowClear": true,
74
+ "suffix": "search",
75
+ "maxLength": 50
76
+ }
77
+ ```
78
+
79
+ 然后在组件中使用:
80
+
81
+ ```vue
82
+ <template>
83
+ <x-input
84
+ v-model="searchValue"
85
+ query-params-name="customInputConfig"
86
+ @search="handleSearch"
87
+ />
88
+ </template>
89
+ ```
90
+
91
+ ## 注意事项
92
+
93
+ 1. 组件会在每次输入值变化时触发 `search` 事件
94
+ 2. 点击前缀/后缀图标或按下回车键也会触发 `search` 事件
95
+ 3. 组件通过 `v-model` 支持双向绑定
@@ -25,7 +25,7 @@
25
25
  </template>
26
26
  <template v-else-if="cell.type === 'slot'">
27
27
  <template
28
- v-if="['x-form-table','x-add-native-form','x-tree-pro', 'x-his-editor', 'x-tab', 'x-form-group', 'x-report', 'x-buttons', 'x-label-select', 'x-conversation', 'x-check-list', 'x-cardSet', 'x-collapse','x-h-descriptions', 'x-sidebar', 'x-list','x-input','x-time-line', 'x-radio','x-calendar', 'x-time-select' ,'x-checkbox', 'x-title'].includes(cell.slotType)">
28
+ v-if="['x-form-table','x-add-native-form','x-tree-pro', 'x-his-editor', 'x-tab', 'x-form-group', 'x-report', 'x-buttons', 'x-label-select', 'x-conversation', 'x-check-list', 'x-cardSet', 'x-collapse','x-h-descriptions', 'x-sidebar', 'x-list','x-input','x-time-line', 'x-radio','x-calendar', 'x-time-select' ,'x-checkbox', 'x-title', 'x-select'].includes(cell.slotType)">
29
29
  <component
30
30
  :is="getComponentName(cell.slotConfig, cell.serviceName, cell.slotType)"
31
31
  :key="cellIndex"
@@ -60,7 +60,7 @@
60
60
  </template>
61
61
  <template v-else-if="cell.type === 'slot'">
62
62
  <template
63
- v-if="['x-form-table','x-add-native-form','x-tree-pro', 'x-his-editor', 'x-tab', 'x-form-group', 'x-report', 'x-buttons', 'x-label-select', 'x-conversation', 'x-check-list', 'x-cardSet', 'x-collapse', 'x-h-descriptions', 'x-sidebar', 'x-list','x-input','x-time-line', 'x-radio','x-calendar', 'x-time-select','x-checkbox', 'x-title'].includes(cell.slotType)">
63
+ v-if="['x-form-table','x-add-native-form','x-tree-pro', 'x-his-editor', 'x-tab', 'x-form-group', 'x-report', 'x-buttons', 'x-label-select', 'x-conversation', 'x-check-list', 'x-cardSet', 'x-collapse', 'x-h-descriptions', 'x-sidebar', 'x-list','x-input','x-time-line', 'x-radio','x-calendar', 'x-time-select','x-checkbox', 'x-title', 'x-select'].includes(cell.slotType)">
64
64
  <component
65
65
  :is="getComponentName(cell.slotConfig, cell.serviceName, cell.slotType)"
66
66
  :key="cellIndex"
@@ -116,7 +116,8 @@ export default {
116
116
  XCalendar: () => import('@vue2-client/base-client/components/common/XCalendar/XCalendar.vue'),
117
117
  XTimeSelect: () => import('@vue2-client/base-client/components/his/XTimeSelect/XTimeSelect.vue'),
118
118
  XCheckbox: () => import('@vue2-client/base-client/components/his/XCheckbox/XCheckbox.vue'),
119
- XTitle: () => import('@vue2-client/base-client/components/his/XTitle/XTitle.vue')
119
+ XTitle: () => import('@vue2-client/base-client/components/his/XTitle/XTitle.vue'),
120
+ XSelect: () => import('@vue2-client/base-client/components/his/XSelect/XSelect.vue')
120
121
  },
121
122
  props: {
122
123
  // 每一行的配置
@@ -1,51 +1,29 @@
1
1
  <template>
2
2
  <div class="patient-info-descriptions">
3
- <a-descriptions
4
- :column="config?.layout"
5
- :size="config?.style?.size"
6
- :bordered="config?.style?.bordered"
7
- layout="horizontal">
8
- <template v-if="data">
9
- <!-- 显示前N个标签 -->
10
- <a-descriptions-item
11
- v-for="(item) in visibleItems"
12
- :key="item.field"
13
- :colon="item.colon !== false"
14
- v-if="data[item.field] !== null && data[item.field] !== undefined">
15
- <template #label>
16
- <div :class="['label-wrapper', { 'with-avatar': item.showAvatar }]">
17
- <a-avatar
18
- v-if="item.showAvatar"
19
- :size="item.avatar.size"
20
- :icon="item.avatar.icon"
21
- :style="{ background: item.avatar.background }"
22
- />
23
- <span class="label-text">{{ item.label }}</span>
24
- </div>
25
- </template>
26
- <div class="content-wrapper">
27
- {{ data[item.field] }}
28
- </div>
29
- </a-descriptions-item>
3
+ <div class="descriptions-container">
4
+ <!-- 详情/收起按钮 -->
5
+ <div v-if="hasMoreItems" class="detail-button-wrapper">
6
+ <a-button
7
+ :type="config?.detailsConfig?.buttonType || 'link'"
8
+ @click="toggleDetails"
9
+ >
10
+ <a-icon :type="showAllItems ? 'up' : 'down'" />
11
+ {{ showAllItems ? '收起' : (config?.detailsConfig?.buttonText || '详情') }}
12
+ </a-button>
13
+ </div>
30
14
 
31
- <!-- 详情按钮 -->
32
- <a-descriptions-item v-if="hasMoreItems && !showAllItems">
33
- <a-button
34
- :type="config?.detailsConfig?.buttonType || 'link'"
35
- @click="toggleDetails"
36
- >
37
- <a-icon type="down" />
38
- {{ config?.detailsConfig?.buttonText || '详情' }}
39
- </a-button>
40
- </a-descriptions-item>
41
-
42
- <!-- 展开后显示剩余标签 -->
43
- <template v-if="showAllItems">
15
+ <a-descriptions
16
+ :column="config?.layout"
17
+ :size="config?.style?.size"
18
+ :bordered="config?.style?.bordered"
19
+ layout="horizontal">
20
+ <template v-if="data">
21
+ <!-- 显示前N个标签 -->
44
22
  <a-descriptions-item
45
- v-for="item in hiddenItems"
23
+ v-for="(item) in visibleItems"
46
24
  :key="item.field"
47
25
  :colon="item.colon !== false"
48
- v-if="data[item.field] !== null && data[item.field] !== undefined">
26
+ v-if="data[item.field] !== null && data[item.field] !== undefined && (!showAllItems || showAllItems && !hiddenItems.some(i => i.field === item.field))">
49
27
  <template #label>
50
28
  <div :class="['label-wrapper', { 'with-avatar': item.showAvatar }]">
51
29
  <a-avatar
@@ -62,16 +40,32 @@
62
40
  </div>
63
41
  </a-descriptions-item>
64
42
 
65
- <!-- 收起按钮 -->
66
- <a-descriptions-item>
67
- <a-button type="link" @click="toggleDetails">
68
- <a-icon type="up" />
69
- 收起
70
- </a-button>
71
- </a-descriptions-item>
43
+ <!-- 展开后显示剩余标签 -->
44
+ <template v-if="showAllItems">
45
+ <a-descriptions-item
46
+ v-for="item in hiddenItems"
47
+ :key="item.field"
48
+ :colon="item.colon !== false"
49
+ v-if="data[item.field] !== null && data[item.field] !== undefined">
50
+ <template #label>
51
+ <div :class="['label-wrapper', { 'with-avatar': item.showAvatar }]">
52
+ <a-avatar
53
+ v-if="item.showAvatar"
54
+ :size="item.avatar.size"
55
+ :icon="item.avatar.icon"
56
+ :style="{ background: item.avatar.background }"
57
+ />
58
+ <span class="label-text">{{ item.label }}</span>
59
+ </div>
60
+ </template>
61
+ <div class="content-wrapper">
62
+ {{ data[item.field] }}
63
+ </div>
64
+ </a-descriptions-item>
65
+ </template>
72
66
  </template>
73
- </template>
74
- </a-descriptions>
67
+ </a-descriptions>
68
+ </div>
75
69
  </div>
76
70
  </template>
77
71
  <script>
@@ -89,7 +83,7 @@ export default {
89
83
  props: {
90
84
  queryParamsName: {
91
85
  type: String,
92
- default: 'DescriptionsExampleConfig'
86
+ default: ''
93
87
  },
94
88
  parameter: {
95
89
  type: Object,
@@ -126,8 +120,18 @@ export default {
126
120
  // 获取应该显示的标签
127
121
  visibleItems () {
128
122
  if (!this.config?.items) return []
129
- // 如果没有配置detailsConfig或者已经展开,则显示所有项
130
- if (!this.config?.detailsConfig || this.showAllItems) return this.config.items
123
+
124
+ if (!this.config?.detailsConfig) {
125
+ // 如果没有配置detailsConfig,直接显示所有项
126
+ return this.config.items
127
+ }
128
+
129
+ if (this.showAllItems) {
130
+ // 如果已经展开,也返回前N项,后面的项目会通过hiddenItems显示
131
+ return this.config.items.slice(0, this.detailsAfterIndex)
132
+ }
133
+
134
+ // 否则显示前N项
131
135
  return this.config.items.slice(0, this.detailsAfterIndex)
132
136
  },
133
137
  // 获取隐藏的标签
@@ -183,6 +187,20 @@ export default {
183
187
  border-radius: 4px;
184
188
  }
185
189
 
190
+ .descriptions-container {
191
+ position: relative; /* 为绝对定位的按钮提供参考点 */
192
+ }
193
+
194
+ .detail-button-wrapper {
195
+ position: absolute;
196
+ right: 0;
197
+ top: 0;
198
+ z-index: 10;
199
+ padding: 4px 8px;
200
+ border-radius: 4px;
201
+ background-color: rgba(255, 255, 255, 0.9);
202
+ }
203
+
186
204
  .label-wrapper {
187
205
  display: flex;
188
206
  align-items: center;
@@ -7,7 +7,8 @@
7
7
  - 支持响应式布局,在不同屏幕尺寸下自动调整列数
8
8
  - 支持折叠/展开功能,控制信息的显示量
9
9
  - 支持自定义样式和头像
10
- - 自动隐藏值为 nullundefined 的字段
10
+ - 自动隐藏值为 nullundefined的字段
11
+ - 右上角悬浮"详情/收起"按钮,布局更整洁
11
12
 
12
13
  ## 配置说明
13
14
 
@@ -17,18 +18,19 @@
17
18
 
18
19
  ```json
19
20
  "layout": {
20
- "xl": 8,
21
+ "xl": 4,
21
22
  "md": 3,
22
23
  "sm": 2,
23
24
  "lg": 4,
24
25
  "xs": 1,
25
- "xxl": 8
26
+ "xxl": 5
26
27
  }
27
28
  ```
28
29
 
29
30
  - 用于设置在不同屏幕尺寸下的列数
30
31
  - 键名对应不同的屏幕尺寸(xs, sm, md, lg, xl, xxl)
31
32
  - 值为每行显示的列数
33
+ - 建议根据字段宽度合理设置,避免一行过多字段导致布局问题
32
34
 
33
35
  ### 2. 样式配置 (style)
34
36
 
@@ -84,7 +86,7 @@
84
86
  "detailsConfig": {
85
87
  "buttonText": "详细",
86
88
  "buttonType": "link",
87
- "showAfterIndex": 3
89
+ "showAfterIndex": 5
88
90
  }
89
91
  ```
90
92
 
@@ -105,14 +107,14 @@
105
107
  ```
106
108
 
107
109
  - `logicName`: 用于获取数据的逻辑名称
108
- - `serverName`: 服务名称
109
- - `parameter`: 可选的参数对象
110
+ - `serverName`: 服务名称,默认为 "af-his"
111
+ - `parameter`: 可选的参数对象,会与组件的 parameter 属性合并后传递给后端
110
112
 
111
113
  ## 使用示例
112
114
 
113
115
  ```vue
114
116
  <template>
115
- <XHDescriptions queryParamsName="DescriptionsExampleConfig" />
117
+ <XHDescriptions queryParamsName="patientBasicInfoConfig" />
116
118
  </template>
117
119
 
118
120
  <script>
@@ -126,38 +128,70 @@ export default {
126
128
  </script>
127
129
  ```
128
130
 
131
+ 也可以传递参数:
132
+
133
+ ```vue
134
+ <template>
135
+ <XHDescriptions
136
+ queryParamsName="patientBasicInfoConfig"
137
+ :parameter="{ patientId: '123456' }"
138
+ />
139
+ </template>
140
+ ```
141
+
129
142
  ## 特殊说明
130
143
 
131
144
  1. **响应式布局**:
132
145
  - 组件会根据屏幕尺寸自动调整列数
133
146
  - 可在 `layout` 配置中自定义不同尺寸下的列数
147
+ - 建议根据字段数量和宽度合理设置列数,例如字段较多时可减少列数
134
148
 
135
149
  2. **折叠/展开功能**:
136
150
  - 折叠功能只在明确配置了 `detailsConfig` 对象时启用
137
151
  - 没有配置 `detailsConfig` 时,会直接显示所有字段,不会出现详情按钮
138
- - 配置了 `detailsConfig` 后,只显示 `showAfterIndex` 指定数量的字段,超出部分需点击详情按钮查看
139
- - 点击详情按钮后会展开显示全部字段,可通过收起按钮恢复
152
+ - 详情按钮会悬浮在组件右上角,点击可展开/收起全部字段
153
+ - 按钮显示"详情"或"收起",会根据当前状态自动切换
140
154
 
141
155
  3. **空值处理**:
142
- - 当字段值为 `null` `undefined` 时,该字段不会显示
143
- - 折叠功能会忽略值为 `null` 的字段,只有有值的隐藏字段才会触发显示详情按钮
156
+ - 当字段值为 `null`、`undefined` 或空字符串 `""` 时,该字段不会显示
157
+ - 折叠功能会忽略空值的字段,只有有值的隐藏字段才会触发显示详情按钮
144
158
  - 这个特性可以用于根据数据动态控制字段的显示,无需修改配置
145
159
 
160
+ 4. **字段重复问题处理**:
161
+ - 组件已经优化处理了展开时的字段重复显示问题
162
+ - 采用智能过滤机制,确保同一字段不会在多个位置显示
163
+
164
+ ## 配置优化建议
165
+
166
+ 1. **字段数量**:
167
+ - 建议将最重要、最常用的信息放在前 `showAfterIndex` 个位置
168
+ - 避免配置过多字段,通常不建议超过 20 个字段
169
+ - 如有大量相关字段,考虑拆分为多个不同的描述组件
170
+
171
+ 2. **布局优化**:
172
+ - 根据字段的宽度调整 `layout` 配置
173
+ - 较宽的字段(如地址、证件号等)可能需要更多的列宽
174
+ - 建议在大屏幕下(xl, xxl)不要超过 4-5 列
175
+
176
+ 3. **样式设置**:
177
+ - 为了统一视觉效果,建议所有相似的描述组件使用统一的样式设置
178
+ - 字段宽度设置合理的 `minWidth`,确保在各种屏幕下显示正常
179
+
146
180
  ## 配置示例
147
181
 
148
182
  ### 启用折叠功能示例
149
183
 
150
- 以下配置将启用折叠功能,只显示前3个字段,其余字段需点击"详细"按钮查看:
184
+ 以下配置将启用折叠功能,只显示前6个字段,其余字段需点击"详细"按钮查看:
151
185
 
152
186
  ```json
153
187
  {
154
188
  "layout": {
155
- "xl": 8,
189
+ "xl": 4,
156
190
  "md": 3,
157
191
  "sm": 2,
158
- "lg": 4,
192
+ "lg": 3,
159
193
  "xs": 1,
160
- "xxl": 8
194
+ "xxl": 5
161
195
  },
162
196
  "style": {
163
197
  "size": "small",
@@ -190,15 +224,31 @@ export default {
190
224
  {
191
225
  "field": "age",
192
226
  "label": "年龄"
227
+ },
228
+ {
229
+ "field": "diagnosis",
230
+ "label": "诊断"
231
+ },
232
+ {
233
+ "field": "phone_no",
234
+ "label": "电话号码"
235
+ },
236
+ {
237
+ "field": "identification_no",
238
+ "label": "身份证号"
239
+ },
240
+ {
241
+ "field": "allergies",
242
+ "label": "过敏史"
193
243
  }
194
244
  // ... 更多字段
195
245
  ],
196
246
  "detailsConfig": {
197
247
  "buttonText": "详细",
198
248
  "buttonType": "link",
199
- "showAfterIndex": 3 // 设置为3表示只显示前3个字段,其余字段点击"详细"后显示
249
+ "showAfterIndex": 6 // 设置为6表示只显示前6个字段,其余字段点击"详细"后显示
200
250
  },
201
- "logicName": "patientBasicInfo",
251
+ "logicName": "patientBasicInfoLogic",
202
252
  "serverName": "af-his"
203
253
  }
204
254
  ```
@@ -210,12 +260,12 @@ export default {
210
260
  ```json
211
261
  {
212
262
  "layout": {
213
- "xl": 8,
263
+ "xl": 4,
214
264
  "md": 3,
215
265
  "sm": 2,
216
- "lg": 4,
266
+ "lg": 3,
217
267
  "xs": 1,
218
- "xxl": 8
268
+ "xxl": 5
219
269
  },
220
270
  "style": {
221
271
  "size": "small",
@@ -225,7 +275,7 @@ export default {
225
275
  "items": [
226
276
  // 字段配置...
227
277
  ],
228
- "logicName": "patientBasicInfo",
278
+ "logicName": "patientBasicInfoLogic",
229
279
  "serverName": "af-his"
230
280
  // 没有 detailsConfig,将直接显示所有字段
231
281
  }
@@ -0,0 +1,66 @@
1
+ <template>
2
+ <div>
3
+ <a-select
4
+ v-model="selectedValue"
5
+ :placeholder="placeholder"
6
+ style="width: 150px"
7
+ :allowClear="true"
8
+ :showSearch="true"
9
+ @change="handleChange"
10
+ >
11
+ <a-select-option v-for="(item, index) in data" :key="index" :value="item.value">
12
+ {{ item.label }}
13
+ </a-select-option>
14
+ </a-select>
15
+ </div>
16
+ </template>
17
+
18
+ <script>
19
+ import { runLogic } from '@vue2-client/services/api/common'
20
+
21
+ export default {
22
+ name: 'XSelect',
23
+ props: {
24
+ queryParamsName: {
25
+ type: Object,
26
+ default: null
27
+ }
28
+ },
29
+ data () {
30
+ return {
31
+ data: [],
32
+ selectedValue: undefined,
33
+ placeholder: ''
34
+ }
35
+ },
36
+ created () {
37
+ this.getData(this.queryParamsName)
38
+ },
39
+ methods: {
40
+ async getData (config) {
41
+ try {
42
+ const res = await runLogic(config, {}, 'af-his')
43
+ this.data = res.data || []
44
+ if (res.placeholder) {
45
+ this.placeholder = res.placeholder
46
+ }
47
+ this.selectedValue = undefined
48
+ } catch (error) {
49
+ console.error('获取数据失败:', error)
50
+ }
51
+ },
52
+ handleChange (value) {
53
+ this.selectedValue = value === '' ? undefined : value
54
+ this.$emit('change', this.selectedValue)
55
+ },
56
+ },
57
+ watch: {
58
+ selectedValue (val) {
59
+ }
60
+ }
61
+ }
62
+ </script>
63
+
64
+ <style scoped>
65
+
66
+ </style>
@@ -1,20 +1,14 @@
1
1
  <template>
2
2
  <div>
3
3
  <div class="text-card">
4
- <!-- 添加顶部按钮区域 -->
5
- <div class="text-card-header">
6
- <div class="add-button" @click="handleAddClick">
7
- <span class="plus-icon">+</span>
8
- </div>
9
- </div>
10
4
  <div
11
5
  v-for="(item, index) in displayItems"
12
6
  :key="index"
13
7
  class="text-item"
14
- @click="handleItemClick(index)"
15
- @contextmenu.prevent="handleContextMenu(index)">
8
+ @click="handleItemClick(index, item.id)"
9
+ @contextmenu.prevent="handleContextMenu(index, item.id)">
16
10
  <div class="text-content" :class="{ 'is-empty': !item.content }">
17
- {{ item.content || '点击添加内容' }}
11
+ {{ item.content || '+' }}
18
12
  </div>
19
13
  </div>
20
14
  <!-- 编辑弹出框 -->
@@ -49,7 +43,7 @@ import { runLogic } from '@vue2-client/services/api/common'
49
43
  export default {
50
44
  name: 'XTextCard',
51
45
  props: {
52
- // logic配置名,要求返回一个数组[]
46
+ // logic配置名,要求返回一个数组[{ id: , content: }]
53
47
  queryParamsName: {
54
48
  type: String,
55
49
  default: 'memorandumLOGIC'
@@ -72,6 +66,7 @@ export default {
72
66
  editingContent: '', // 编辑框的内容
73
67
  showDeleteModal: false, // 是否显示删除确认框
74
68
  deleteIndex: -1, // 要删除的项目索引
69
+ ID: 0
75
70
  }
76
71
  },
77
72
  computed: {
@@ -86,18 +81,9 @@ export default {
86
81
  }
87
82
  },
88
83
  methods: {
89
- // 添加新方法
90
- handleAddClick () {
91
- // 直接打开编辑框,索引设为数组长度(等同于点击最后一个空行)
92
- this.editingIndex = this.localItems.length
93
- this.editingContent = ''
94
- this.showEdit = true
95
- this.$nextTick(() => {
96
- this.$refs.textarea?.focus()
97
- })
98
- },
99
84
  // 点击项目
100
- handleItemClick (index) {
85
+ handleItemClick (index, id) {
86
+ this.ID = id
101
87
  this.editingIndex = index
102
88
  this.editingContent = this.displayItems[index].content
103
89
  this.showEdit = true
@@ -119,8 +105,9 @@ export default {
119
105
  // 否则更新现有内容
120
106
  this.localItems[this.editingIndex].content = this.editingContent.trim()
121
107
  }
108
+ const addData = { data: this.editingContent.trim(), ID: this.ID }
122
109
  // 触发更新事件
123
- this.$emit('add', this.editingContent.trim())
110
+ this.$emit('add', addData)
124
111
  }
125
112
  this.showEdit = false
126
113
  this.editingIndex = -1
@@ -130,15 +117,16 @@ export default {
130
117
  async initData (data, parameter) {
131
118
  // 从配置中获取数据
132
119
  const resData = await runLogic(data, parameter, 'af-his')
133
- this.localItems = resData.map(content => ({ content: content }))
120
+ this.localItems = resData
134
121
  return resData
135
122
  },
136
123
  // 处理右键菜单
137
- handleContextMenu (index) {
124
+ handleContextMenu (index, id) {
138
125
  // 如果是最后一个空行,不显示删除框
139
126
  if (index === this.localItems.length) {
140
127
  return
141
128
  }
129
+ this.ID = id
142
130
  this.deleteIndex = index
143
131
  this.showDeleteModal = true
144
132
  },
@@ -146,7 +134,8 @@ export default {
146
134
  handleDeleteConfirm () {
147
135
  if (this.deleteIndex > -1) {
148
136
  this.localItems.splice(this.deleteIndex, 1)
149
- this.$emit('delete', [...this.localItems][this.deleteIndex])
137
+ const addData = { data: [...this.localItems][this.deleteIndex], ID: this.ID }
138
+ this.$emit('delete', addData)
150
139
  this.deleteIndex = -1
151
140
  }
152
141
  this.showDeleteModal = false
@@ -168,37 +157,12 @@ export default {
168
157
  .text-card {
169
158
  position: relative;
170
159
  width: 100%;
171
- // 添加头部样式
172
- .text-card-header {
173
- padding: 8px 12px;
174
- border-bottom: 1px solid #f0f0f0;
175
- .add-button {
176
- display: inline-flex;
177
- align-items: center;
178
- justify-content: center;
179
- width: 24px;
180
- height: 24px;
181
- border-radius: 4px;
182
- cursor: pointer;
183
- transition: all 0.3s;
184
- &:hover {
185
- background-color: #f5f5f5;
186
- }
187
- .plus-icon {
188
- font-size: 20px;
189
- line-height: 1;
190
- color: #5D5C5C;
191
- font-weight: bold;
192
- }
193
- }
194
- }
195
-
196
160
  .text-item {
197
161
  padding: 8px 12px;
198
162
  border-bottom: 1px solid #f0f0f0;
199
163
  cursor: pointer;
200
164
  transition: all 0.3s;
201
- user-select: none; // 防止文本被选中
165
+ user-select: none;
202
166
 
203
167
  &:hover {
204
168
  background-color: #f5f5f5;
@@ -211,7 +175,8 @@ export default {
211
175
  line-height: 1.5;
212
176
 
213
177
  &.is-empty {
214
- color: #bfbfbf;
178
+ color: #5D5C5C;
179
+ font-weight: 400;
215
180
  }
216
181
  }
217
182
  }
@@ -53,7 +53,8 @@ routerResource.example = {
53
53
  path: 'example',
54
54
  name: '示例主页面',
55
55
  // component: () => import('@vue2-client/base-client/components/his/XTimeSelect/XTimeSelect.vue'),
56
- component: () => import('@vue2-client/base-client/components/his/XTitle/XTitle.vue'),
56
+ // component: () => import('@vue2-client/base-client/components/his/XTitle/XTitle.vue'),
57
+ component: () => import('@vue2-client/base-client/components/his/XSelect/XSelect.vue'),
57
58
  // component: () => import('@vue2-client/base-client/components/his/XRadio/XRadio.vue'),
58
59
  // component: () => import('@vue2-client/base-client/components/his/XList/XList.vue'),
59
60
  // component: () => import('@vue2-client/base-client/components/common/XCollapse/XCollapse.vue'),